对象之间的映射与转换


在开发的时候经常会有业务代码之间有很多的 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>

关于该库的最简单的使用,先在 bean 工厂中塞入这个 bean

    <!-- 配置工程中需要的dozer -->
    <bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
        <property name="mappingFiles" value="classpath*:dozer-bean-mappings.xml"/>
    </bean>

然后在代码中注入这个 bean,随后使用 map 方法直接转换即可

    @Resource
    private Mapper dozer;
    
    dozer.map(image, ImageVM.class);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值