Mapstruct---一种比BeanUtils更快的转换方式

一、Mapstruct简介

   MapStruct是用于生成类型安全的bean映射类的Java注解处理器。
   你所要做的就是定义一个映射器接口,声明任何需要映射的方法。在编译过程中,MapStruct将生成该接口的实现。此实现使用纯Java的方法调用源对象和目标对象之间进行映射,并非Java反射机制。
   与手工编写映射代码相比,MapStruct通过生成冗长且容易出错的代码来节省时间。在配置方法的约定之后,MapStruct使用了合理的默认值,但在配置或实现特殊行为时将不再适用。

与动态映射框架相比,MapStruct具有以下优点:

  • 使用纯Java方法代替Java反射机制快速执行
  • 编译时类型安全:只能映射彼此的对象和属性,不能映射一个Order实体到一个Customer DTO中等等
  • 如果无法映射实体或属性,则在编译时清楚错误报告

二、Mapstruct原理

MapStruct是基于JSR 269的Java注解处理器,因此可以在命令行构建中使用(javac、Ant、Maven等等),也可以在IDE内使用。

它包括以下工件:

  • org.mapstruct:mapstruct:包含了必要的注解,例如@Mapping;在Java 8或更高版本中,使用org.mapstruct:mapstruct-jdk8,而不是利用Java 8中引入的语言进行改进。
  • org.mapstruct:mapstruct-processor:包含生成映射器实现的注解处理器

在使用过程中需要只需要配置完成后运行 mvn compile就会发现 target文件夹中生成了一个mapper接口的实现类。打开实现类会发现实体类中自动生成了字段一一对应的get、set方法的文件。

这就是为什么mapstruct的效率比较高的原因相比于反射获取对象进行拷贝的方法,这种更贴近于原生get、set方法的框架显得更为高效。

这个文件是通过在mapper中的注解,使用生成映射器的注解处理器从而自动生成了这段代码。
看到这里是不是感觉JSR 269注解处理器很熟悉。确实在很多地方都是用到了他,在我之前了解lombok原理时也看到他的身影。那么总让我在这里好好介绍一下他。

三、注解处理器

1.Java代码编译过程

Java代码编译和执行的整个过程包含了以下三个重要的机制:1)Java源码编译机制;2)类加载机制;3)类执行机制

其中,Java源码编译由以下三个过程组成:1)分析和输入到符号表;2)注解处理;3)语义分析和生成class文件

流程图如下所示:

其中的annotation processing就是代码的注解处理,jdk7之前访问和处理Annotation的工具统称APT(Annotation Processing Tool)(jdk7后就被废除了),jdk7及之后采用了JSR 269 API。

2.注解处理器的作用

Annotation就像代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取。读取到了程序元素的元数据,就可以执行相应的处理。通过注解,程序开发人员可以在不改变原有逻辑的情况下,在源代码文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过解析这些注解获取到这些补充信息,从而进行验证或者进行部署等。

————————————————
版权声明:本文为CSDN博主「古柏树下」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_29774479/article/details/90402767

四 使用

mapstruct 专门用来处理 domin 实体类与 model 类的属性映射的,我们只需定义每个实体的mapper转换接口,mapstruct 在编译的时候就会自动的帮我们实现这个映射接口,避免了麻烦复杂的映射实现
简单说:快捷实现domain 实体与DTO 、VO实体的映射转化。

使用
引入
在pom.xml中引入jar包

    
    
        org.mapstruct
        mapstruct
        1.3.1.Final
    
    
    
        org.mapstruct
        mapstruct-processor
        1.3.1.Final
    

注:mapstruct-jdk8 这种引用方式官方已经废弃

 
    org.mapstruct
    mapstruct-jdk8
    1.3.1.Final

使用方式

spring 中注入:

@Mapper
public interface UserRoleMapper {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param user 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);
@RestController
public class DemoMapstructApplication {

    @Autowired
    private UserRoleMapper  mapper;
    
    @GetMapping("convert3")
    public String convertEntity3() {
        Role role  = new Role(2L, "administrator", "超级管理员");
        user  = new User(1L, "zhangsan", "12345", "17677778888", "[email protected]", role);
        UserRoleDto userRoleDto = mapper.toUserRoleDto(user);
        return userRoleDto.toString();
    }
    
}

javabeen:

@Data
@ToString
public class StudentDto {
    private String name;
    private String status;
    private Integer age;
}
@Data
@AllArgsConstructor
public class StudentEntity {
    private String name;
    private String status;
    private Integer age;
    private String girlFriend;
}
/**
 * @author zyh
 */
@Mapper
public interface StudentObjMapping  {
    StudentObjMapping INSTANCES=Mappers.getMapper(StudentObjMapping.class);


    StudentDto toStudentDto(StudentEntity entity);
}

注意哟,女朋友没带过来,因为字段没对应的问题
public class ObjTraTest {
    public static void main(String[] args) {
        StudentEntity studentDto=new StudentEntity("zyh","1",24,"zmy");
        StudentDto dto = StudentObjMapping.INSTANCES.toStudentDto(studentDto);
        System.out.println(dto.toString());
    }
}

如果我们两个实体有不同的名字咋办嘞?
参考下列demo,

/** @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param user 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
*/
  @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);

另外这里支持多数据源,只要我们把源和目标对应就好。

多家俩类,并更新下mapping

@Data
@ToString
public class People {
    private String peopleName;
    private int age;
    private String status;
    private String home;
}
@Data
@AllArgsConstructor
public class StudentHome {
    private String homeAddr;
}
@Mapper
public interface StudentObjMapping  {
    StudentObjMapping INSTANCES=Mappers.getMapper(StudentObjMapping.class);


    StudentDto toStudentDto(StudentEntity entity);

    @Mappings({
            @Mapping(source = "student.name",target = "peopleName"),
            @Mapping(source = "home.homeAddr",target = "home")
    })
    People toPeople(StudentEntity student,StudentHome home);
}
完美

对于List的转换,且其各元素有特定的对应关系,需要先些普通实体的转换,再写list的转换

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;

public interface UserMapping {

    /**
     * Student 转化为 User
     * @param Student
     * @return
     */
     @Mappings({
            @Mapping(target = "uname", source = "sname")
          // 多个属性不对应可以用 "," 隔开编写多个@Mapping
          // ,@Mapping(target = "uname", source = "sname")
    })
     User studentToUser(Student student);
     
     
    /**
     * 此时 studentsToUsers 的实现为循环调用 studentToUser 并继承了 studentToUser 的属性映射
     * Students 转化为 Users
     * @param Students
     * @return
     */
     List studentsToUsers(List students);
}


问题总结

MapStruct需要Impl类

参考: java - MapStruct需要实现类
没有引入 mapstruct-processor-xx 包导致的

参考

MapStruct原理分析
MapStruct超级简单的学习笔记_qq122516902的博客-CSDN博客_mapstruct

干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!

你可能感兴趣的:(Mapstruct---一种比BeanUtils更快的转换方式)