目录
mapstruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。
实体类映射框架大致有两种:一种是运行期通过java反射机制动态映射;另一种是编译期动态生成getter/setter,在运行期直接调用框架编译好的class类实现实体映射。
由于mapstruct映射是在编译期间实现的,因此相比运行期的映射框架有以下几个优点:
mapstruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269使用Annotation Processor在编译期间处理注解,Annotation Processor相当于编译器的一种插件,因此又称为插入式注解处理。想要实现JSR 269,主要有以下几个步骤:
Java程序编译一般经历以下流程:
图1 Java程序编译流程
上图中Java源码到class文件的过程其实是一个比较复杂的过程。其中的经过可以用下图描述:
图2 mapstruct编译过程
上图的流程可以概括为下面几个步骤:
mapstruct主要有两个包:
两个包大致包含的类如下图所示:
图3org.mapstruct:mapstruct结构
图4org.mapstruct:mapstruct-processor结构
mapstruct的用法很简单,假设我们有两个实体类UserDto和UserVo,类定义如下:
public class UserDTO {
private String name;
private int age;
private Date birthday;
private int gender;
private String idCard;
}
public class UserVo {
private String userName;
private int age;
private Date birthday;
private int gender;
private String idCard;
}
然后需要定义一个用例映射的接口,接口如下:
@Mapper
public interface UserConverter {
@Mappings({
@Mapping(source = "name", target = "userName")
})
UserVo userDtoToVo(UserDto userDto);
}
在UserConverter接口上添加@Mapper注解,在编译的时候,mapstruct自动会生成一个UserConverter的实现类,实现userDtoToVo方法。userDtoToVo方法上的@Mappings主要用于特殊映射,比如上述的UserDto中的name想要映射成userName,则通过@Mappings告诉mapstruct将source中的name映射成target中的userName字段。
通过mapstruct处理后,自动生成UserConverterImpl类,类实现代码如下所示:
@Override
public UserVo userDtoToVo(UserDto userDto) {
if ( userDto == null ) {
return null;
}
UserVo userVo = new UserVo();
if ( userDto.getName() != null ) {
userVo.setUserName( userDto.getName() );
}
userVo.setAge( userDto.getAge() );
if ( userDto.getBirthday() != null ) {
userVo.setBirthday( userDto.getBirthday() );
}
if ( userDto.getGender() != null ) {
userVo.setGender( Integer.parseInt( userDto.getGender() ) );
}
if ( userDto.getIdCard() != null ) {
userVo.setIdCard( userDto.getIdCard() );
}
return userVo;
}
想要了解原理,可以从mapstruct的源码入手。上文介绍过mapstruct主要有两个jar包,通过根据JSR 269可以知道MappingProcessor就是mapstruct的入口。com.sun.tools.javac.main.JavaCompiler#compile方法的有段代码在编译的时候会调用到MappingProcessor。compile代码如图5所示:
图5 compile方法
MappingProcessor的process方法具体代码如下所示:
@SupportedAnnotationTypes({"org.mapstruct.Mapper"})
@SupportedOptions({"mapstruct.suppressGeneratorTimestamp", "mapstruct.suppressGeneratorVersionInfoComment", "mapstruct.unmappedTargetPolicy", "mapstruct.defaultComponentModel"})
public class MappingProcessor extends AbstractProcessor {
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (!roundEnvironment.processingOver()) {
RoundContext roundContext = new RoundContext(this.annotationProcessorContext);
Set deferredMappers = this.getAndResetDeferredMappers();
this.processMapperElements(deferredMappers, roundContext);
Set mappers = this.getMappers(annotations, roundEnvironment);
this.processMapperElements(mappers, roundContext);
}
return false;
}
想要用idea在编译的时候调试代码,可以根据下述步骤进行调试:
图6 终端执行mvnDebug命令
图7 idea 创建Remote JVM Debug
通过调试,我们可以看到执行的步骤是这样的:JavaCompiler#compile->AbstractProcessor#init->MappingProcessor#init->MappingProcessor#process。
MappingProcessor#process的代码如下所示:
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (!roundEnvironment.processingOver()) {
RoundContext roundContext = new RoundContext(this.annotationProcessorContext);
Set deferredMappers = this.getAndResetDeferredMappers();
this.processMapperElements(deferredMappers, roundContext);
Set mappers = this.getMappers(annotations, roundEnvironment);
this.processMapperElements(mappers, roundContext);
}
return false;
}
上述代码的this.processMapperElements(mappers, roundContext);就会执行具体的代码改造工作。具体代码就不做展示,可以自行跟进去查看。从processMapperElements可以看到会执行ModelElementProcessor接口的实现类里的process方法。分析下ModelElementProcessor可以发现它有多个实现类,具体实现类如图8所示。
图8ModelElementProcessor实现类
ModelElementProcessor的实现类有多个,每个实现类有不同的作用,并且每个实现类都有一个priority,达到执行顺序的效果,实现类大致有如下几个:
MethodRetrievalProcessor:解析元素的方法等基本信息。priority=1。
MapperCreationProcessor:初始化MapperReference,解析出Mapper。priority=1000。
AnnotationBasedComponentModelProcessor:处理ComponentModel相关逻辑。priority=1100。AnnotationBasedComponentModelProcessor又有3个子类,主要用于实现JSR330、Spring component及Cdi 组件等功能。
MapperRenderingProcessor:创建接口的具体实现类,比如UserConverter接口,则生成UserConverterImpl类。priority=9999。
MapperServiceProcessor:处理spi和META-INF/services/下的相关逻辑。priority=10000。
从MapperRenderingProcessor类里可以看到有个createSourceFile方法,该方法会创建UserConverterImpl类,并写到特定目录下。这样就生成了UserConverter的实现类,里面有UserConverter里的所有方法。
本文主要介绍mapstruct的实现原理。先简单介绍mapstruct是什么,然后对比mapstruct与其他框架的优势,最后分析mapstruct底层原理。分析mapstruct从JavaCompiler入手,一步一步往mapstruct里调试,能够清晰地了解底层的实现原理。想要了解某个框架的原理,最好还是从源码入手,自己亲自调试跟踪执行过程才能对原理了解得更透彻。
https://blog.csdn.net/ni_hao_fan/article/details/99445073
https://www.freesion.com/article/9992598813/