工作当中用到了mapStruct, 刚刚使用不求甚解, 仿照同事的代码贴个标签就用起来了, 但其实一点都不懂。下面记录一下由傻逼到入门的知识接收记录。
(190527-0609)
一. mapStruct简单使用
mapStruct用在由DO向DTO转换,或者其他什么两个对象之间的属性拷贝。
过程:
-
先写一个转换接口,定义一下转换的接口方法(入参是被转化对象,出参是转化成为的对象)。
-
把接口贴上个标签,引入maven包,就会在编译时候自动生成转换接口的实现类,把两个对象相同的属性copy的实现方法。
代码:
1. pom文件中引入maven包
<properties>
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
</properties>
<dependencies>
<!--引入mapStruct注解包-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- mapStruct的annotation processors(下面说,可以理解为编译时生成实现类代码的类)-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<!--谷歌的一个spi包,看作是工具包就好(下面说spi)-->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc5</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--maven编译默认5.0,使用maven-compiler-plugin插件指定jdk版本,1.6以上-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version> <!-- or newer version -->
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2. 定义转换接口
import cn.gasin.domain.Car;
import cn.gasin.dto.CarDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* 转换接口
*/
@Mapper
public interface CarMapper {
//1.定义的实例
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
//2.贴上映射标签的转换接口方法,标签内的值是源属性名和目标属性名不同时候添加的
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}
3. maven编译,生成实现类
=
二、 mapStruct的工作原理
mapStruct利用jdk1.6的注解处理器,在编译时候拿到Mapper注解的接口,生成属性拷贝的实现类代码。
过程:
1. mapStruct继承abstructProcessor实现MappingProcessor编译时注解处理器,重写process方法。在其中处理编译时候生成代码的业务
2. 在META-INF文件夹下的services文件夹下创建一个Processer接口全限定名的文件,把实现类MappingProcessor的全限定名贴上去,这是SPI(Service Provider Interface)的用法, 它可以发现我们的注解处理器
3. 总结: 在编译时候,java发现Processor后,生成mappingProcessor对象,每个被编译的类都经过process方法,mapStruct在其中拦截处理,如果是mapper接口的话就按照规则生成转换接口的实现类。
名词介绍:
1. annotation 注解
注解是在源文件中嵌入的Java元数据(附加信息),这些元数据不算是我们的代码,是一些传递给Java的信息(我们也可以拿到这些信息)。是一种类型,类似enum,就是告诉java这个类只能有这几个对象,annotation贴在哪里(类/方法/字段)就是告诉java这个类/方法/字段有啥不一样的标签。
知道了注解是什么,也就可以想一两个用法。
比方说mapStruct里面,它就是在编译时候拿到了自己的@Mapper注解(怎么拿到下面说),拿到了注解后就知道了源类和目标类。然后通过反射拿到属性,同名的属性互相拷贝。不同名的属性根据@Mapping注解的source和target进行拷贝。。。(还有很多高级的功能,我不太清楚)
Spring里面的注解配置,都是拿到注解信息后进行的。
2. processor 注解处理器
javax.annotation.processing.Processor 接口是JDK1.6提供的专门编译时处理注解的接口,继承它,就可以在编译时候在processing方法种拿到想要处理的注解。
对于mapStruct,在编译时后就拿到@Mapper注解信息和注解贴在的接口,然后生成转换的实现类。
3. SPI 服务发现
java.util.ServiceLoader进行服务实现的发现与实例化,和反射类似,属于运行时的动态技术,在运行时候使用ServiceLoader拿到接口的实现类。
使用对应库Jar包中需要在META-INF/services下放入名称为接口Api完整类名的文件,并填入对应实现类名(如Jar包内有多个实现,则用换行进行分隔)
Processor和SPI没什么关系,Javac进行注解处理的时候,也使用了SPI技术