在开发过程中总会遇到Java对象相互转换,出了最原始及最高效的get/set一块,Java平台中,也出现了很多对象转换工具.

今天本是父亲节,也是地球百年罕见的一次日食现象,晨到点就抱着相机出门,结果专业度不够,拍了俩月亮回来; 朋友圈也是晒疯了,各种各样的太阳,对都是你们的太阳; 此时的我回到家中,左思右想,这个周末又要颓废了吗,别介,好记性不如烂笔头,写会文章吧;(TM….一堆废话)

好吧,咱进入今天的正题,Java对象转换工具哪家强? 首先,java平台中提供了很多的转换工具,旨在提供工作效率,我给大家列举一下

BeanUtils

Dozer

Orika

MapStruct

ModelMapper

JMapper

此处排名不分先后,专业评测

什么是MapStruct

MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.

啥意思?官方说它是个代码生成器,生成约定的Java bean对象转换.重点是纯方法调用

纯方法调用容我先买个关子,5毛,不能再多了.

还记得最初的BeanUtils吗,对于同属性copy,当年用的贼嗨,慢慢的工作经验和程序的并发出现,发现效率并没有那么高;

还有非同属性copy,中途换了N多,比如居于配置文件的Dozer,写着复杂的xml配置.这些框架的背后无不使用了Java的反射或者代理;

此时,要说今天的主角之前,我觉得咱又必须要先说一下,大名鼎鼎的lombok,很多人都耳目共染,有了它,我们再也不用写get/set了,好嗨呀!!!

那么MapStruct又能让我们少写什么呢,对,这位同学很聪慧,不需要我们写对象转换时的get/set啦.

MapStruct亮点是啥

Multi-layered applications often require to map between different object models (e.g. entities and DTOs). Writing such mapping code is a tedious and error-prone task. MapStruct aims at simplifying this work by automating it as much as possible.

In contrast to other mapping frameworks MapStruct generates bean mappings at compile-time which ensures a high performance, allows for fast developer feedback and thorough error checking.

翻译就算了,直接说重点,Java bean 属性转换,这样的代码写多了,看着冗余,还不好找(密密麻麻),MapStruct在编译的时候,帮你生成对应的映射关系,提供开发效率.

如何使用

导入对应依赖

1.3.1.Finalmapstruct-jdk8已经指向mapstruct,所以导入mapstruct-jdk8已经成为过去式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.3.1.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.3.1.Final</version>
    </dependency>
</dependencies>
  1. mapstruct 含有对应的注解,比如@Mapper
  2. mapstruct-processor 包含Java处理机制,代码生成的关键

编写Java Bean

1
2
3
4
5
6
@Data
public class User implements Serializable {
	private String loginName;

	private String password;
}
1
2
3
4
5
6
@Data
public class UserDto implements Serializable {
	private String username;

	private String password;
}

编写Mapper

1
2
3
4
5
6
7
@Mapper(componentModel = "spring")
public interface UserMapper {
	UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);    

	@Mapping(source = "loginName", target = "username")
	UserDto userToUserDto(User user);
}
  1. @Mapper(componentModel = "spring")使用spring方式注入
  2. UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); 不使用spring注入时,可以使用实例模式,默认
  3. @Mapping(source = "loginName", target = "username") 约定映射关系,多个使用@Mappings

测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
public class DomainTest extends ApplicationTest {

	@Autowired
	private UserMapper userMapper;

	@Test
	public void test(){
		User user = new User();
		user.setLoginName("12321");
		user.setPassword("123456");
        // 实例模式
		log.info("result:{}",UserMapper.INSTANCE.userToUserDto(user));
	}

	@Test
	public void test2(){
		User user = new User();
		user.setLoginName("12321");
		user.setPassword("123456");
        // spring 注入模式
		log.info("result:{}",userMapper.userToUserDto(user));
	}
}

原理

上文讲述了如何使用MapStruct,现在我们来讲述下原理,讲述原理前,我们要知道一个大杀器

Annotation Processor

Annotation Processor注解处理器是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具. 简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容.

编译时注解处理器AnnotationProcessor的使用

查看lombokMapStruct源码,我们都发现有这样一个文件

META-INF/services/javax.annotation.processing.Processor

内容大概是这样的,它在告诉javac对应的注解处理是哪些

1
2
3
4
5
# Copyright MapStruct Authors.
#
# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0

org.mapstruct.ap.MappingProcessor

通过MappingProcessor一系列的处理后,MapStruct为你生成了对应的mapper的实现类,我们来看看生成的代码怎样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Component
public class UserMapperImpl implements UserMapper {
    public UserMapperImpl() {
    }

    public UserDto userToUserDto(User user) {
        if (user == null) {
            return null;
        } else {
            UserDto userDto = new UserDto();
            userDto.setUsername(user.getLoginName());
            userDto.setPassword(user.getPassword());
            return userDto;
        }
    }
}

此时,是不是觉得哪些代理、反射都弱爆了,对此你有什么样的看法呢,欢迎留言,我们一起探讨