这里写目录标题
- Lambda 的 builder
- idea 自动生成插件 GenerateAllSetter
- MapStruct
- Dozer
在开发的时候经常会有业务代码之间有很多的 JavaBean 之间的相互转化,比如 PO/DTO/VO/QueryParam 之间的转换问题,本文总结一下各种转换方法
Lambda 的 builder
使用构造器模式快速的生成一个 pojo 对象
@Data
@Builder
public class UserVo {
private Long id;
private String username;
private String password; // 密码
private Integer sex; // 性别
private LocalDate birthday; // 生日
private LocalDateTime createTime; // 创建时间
private String config; // 其他扩展信息,以JSON格式存储
public UserVo toUserVo(User user) {
return User user = new User()
.setId(1L)
.setUsername("zhangsan")
.setSex(1)
.setPassword("abc123")
.setCreateTime(LocalDateTime.now())
.setBirthday(LocalDate.of(1999, 9, 27))
.setConfig("[{\"field1\":\"Test Field1\",\"field2\":500}]");
}
}
idea 自动生成插件 GenerateAllSetter
idea 有款叫 GenerateAllSetter 的插件,使用方法:Alt + Enter,在转换方法中自动生成转换代码
private OperationNonComplianceVo toOperationNonComplianceVo() {
OperationNonComplianceVo operationNonComplianceVo = OperationNonComplianceVo.builder()
.date()
.shopName()
.customerName()
.orderNo()
.orderTime()
.destinationName()
.grabTime()
.defectType()
.reassignmentTime()
.departurePlace()
.contacts()
.orderNum()
.build();
return operationNonComplianceVo;
}
MapStruct
MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。工具可以帮我们实现 JavaBean 之间的转换, 通过注解的方式。同时, 作为一个工具类,相比于手写, 其具有便捷, 不容易出错的特点
MapStruct 的使用首先需要引入依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
由于 MapStruct 在编译时工作,其代码生成原理类似 Lambda。会集成到像 Maven 和 Gradle 这样的构建工具上,我们还必须在<build中/>标签中添加一个插件 maven-compiler-plugin,并在其配置中添加 annotationProcessorPaths
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
做好了前置工作之后就可以使用了,pojo 如下:
@Data
@Accessors(chain = true)
public class User {
private Long id;
private String username;
private String password; // 密码
private Integer sex; // 性别
private LocalDate birthday; // 生日
private LocalDateTime createTime; // 创建时间
private String config; // 其他扩展信息,以JSON格式存储
}
@Data
@Accessors(chain = true)
public class UserVo {
private Long id;
private String username;
private String password;
private Integer gender;
private LocalDate birthday;
private String createTime;
private List<UserConfig> config;
@Data
public static class UserConfig {
private String field1;
private Integer field2;
}
}
@mapper 注解可以直接作用与接口上,实现模型之间的转换,默认让对象中相同名称的属性相互转换,入参为需要转换的对象,返回值为被转换的对象,同时,list 也可以进行转换
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
// 如果有不相同名称的属性需要转换,可以加上 @Mapping 注解
@Mapping(target = "gender", source = "sex")
// 如果有时间类需要转换为字符串或者字符串需要转换为时间类,可以这么写
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
UserVo do2vo(User var1);
@Mapping(target = "sex", source = "gender")
// 转换的时候忽略密码
@Mapping(target = "password", ignore = true)
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
User vo2Do(UserVo var1);
List<UserVo> do2voList(List<User> userList);
default List<UserVo.UserConfig> strConfigToListUserConfig(String config) {
return JSON.parseArray(config, UserVo.UserConfig.class);
}
default String listUserConfigToStrConfig(List<UserVo.UserConfig> list) {
return JSON.toJSONString(list);
}
}
其对应的信息不仅仅来自一个类, 那么, 我们也可以通过配置来实现多到一的转换
@Mapping(target = "periodId", source = "studentDetailsPo.periodId")
@Mapping(target = "studentName", source = "userPo.name")
StudentInfoVo convert(UserPo userPo, StudentDetailsPo studentDetailsPo);
调用接口就可以使用了
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
UserVo userVo = UserConverter.INSTANCE.do2vo(user);
User user = UserConverter.INSTANCE.vo2Do(userVo);
当添加 componentModel=“spring” 时,它会在实现类上自动添加 @Component 注解,这样就能被 Spring 记住 component scan,从而加载到 springContext 中,进而被 @Autowird 注入使用
@Mapper(componentModel="spring")
public interface UserConverter {
}
@SpringBootTest
public class UserConverterTest {
@Resource
private UserConverter userConverter;
}
转换器的位置应该放在这里
简单说一下该库的原理。MapStruct 来生成的代码,其类似于人手写 get 与 set,并且是在编译时生成,比运行时生成数据要快不少
其过程为 JVM 编译时解析该库中的注解(还有一种注解解析方式为运行时解析),本质上就是一个实现了 JSR 269 API 的程序。在使用 javac 的过程中,它产生作用的具体流程如下:
- javac 对源代码进行分析,生成了一棵抽象语法树(AST)
- 运行过程中调用实现了 JSR 269 API 的 MapStruct 程序
- 此时就对第一步骤得到的 AST 进行处理,找到 @Data 注解所在类对应的语法树(AST),然后修改该语法树(AST),增加 getter 和 setter 方法定义的相应树节点
- javac 使用修改后的抽象语法树(AST)生成字节码文件,即给 class 增加新的节点(代码块)
Dozer
Dozer 是 Java Bean 到 Java Bean 映射器,它以递归方式将数据从一个对象复制到另一个对象
支持简单属性映射,复杂类型映射,双向映射,隐式显式映射以及递归映射
另外,Dozer不仅支持属性名称之间的映射,还支持在类型之间自动转换。大多数转换方案都是开箱即用的,如果无法完成映射(比如使用属性名映射的时候,名称不同),还允许通过 xml 配置文件、注解、API 的方式配置映射规则
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.4.0</version>
</dependency>
关于该库的最简单的使用,是直接用 API