抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

APT介绍

在JDK6的时候引入了JSR269的标准,即 Pluggable Annotation Processing API (可插入注解处理API),简称APT。它提供了在编译时期改变一些行为的相关API,例如生成一些新的JAVA文件。

大致处理流程如下图,APT在编译成class文件时执行,在这个过程中可以自定义相关行为,例如生成新的源文件(MapStruct),修改AST语法树(Lombok),直到所有编译处理器都没有在对JAVA文件进行修改为止,才会走到后续的生成Class文件的流程。

img

APT实战

前言

在稍微了解一些APT的原理后,我们来尝试写一个基于MapStruct增强Demo,如果不了解MapStruct是啥的,可以去看看相关的[文章](MapStruct从入门到出门(一) - 掘金 (juejin.cn))。

在MapStruct框架中实现对象之间的映射,总要手动创建一个Mapper类型,相对于我们常用的基于反射的BeanUtils来说,就还是不够简单。那么我们能不能自动创建一个Mapper类型呢?我们下面就使用APT来尝试一下把`

创建processor项目

环境 版本
JDK 11
MapStruct 1.5.2
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>pers.orange</groupId>
<artifactId>processor</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<!--Disable annotation processing for ourselves-->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>
目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
─src
│ └─main
│ ├─java
│ │ └─pers
│ │ └─orange
│ │ BeanUtils.java
│ │ CommonMapper.java
│ │ MappingProcessor.java
│ │ To.java
│ │
│ └─resources
│ └─META-INF
│ └─services
│ javax.annotation.processing.Processor
创建注解
1
2
3
4
5
6
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface To {

Class target(); // 所要转换的类
}
创建通用Mapper模板
1
2
3
4
5
6
public interface CommonMapper<S,T> {

T to(S source);

void to(S source, @MappingTarget T target);
}
创建核心Processor类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@SupportedAnnotationTypes("pers.orange.To") // 对被@To注解的类进行扫描
@SupportedSourceVersion( SourceVersion.RELEASE_11 ) // 支持的JDK版本
// 继承AbstractProcessor类,实现process方法
public class MappingProcessor extends AbstractProcessor {
private Messager messager; // 编译输出日志
private Filer filer; // 文件输出

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init( processingEnv );
this.messager = processingEnv.getMessager();
this.filer = processingEnv.getFiler();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith( To.class ); // 获得被@To注解修饰的元素集合
for ( Element element : elements ) {
messager.printMessage( Diagnostic.Kind.NOTE, element.getSimpleName().toString());
To annotation = element.getAnnotation( To.class );
TypeMirror sourceTypeMirror = element.asType(); // 获得源对象类型
TypeMirror targetTypeMirror = getTargetTypeMirror( annotation ); // 获得目标对象类型
createMapperFile( sourceTypeMirror, targetTypeMirror );
}
return false;
}

private TypeMirror getTargetTypeMirror(To annotation){
TypeMirror typeMirror = null;
try {
annotation.target();
}
catch ( MirroredTypeException e ) {
typeMirror = e.getTypeMirror();
}
return typeMirror;
}

private void createMapperFile(TypeMirror sourceTypeMirror,TypeMirror targetTypeMirror){
String sourceName = getSimpleName( sourceTypeMirror );
String targetName = getSimpleName(targetTypeMirror );
String mapperFileName = sourceName+"To"+targetName+"Mapper";
String packageName = getPackageName( sourceTypeMirror );
// 使用JavaPoet框架,简化Java文件的生成,也可以直接使用字符串拼接来实现
TypeSpec typeSpec = TypeSpec.interfaceBuilder( mapperFileName )
.addModifiers( Modifier.PUBLIC )
.addAnnotation( Mapper.class )
.addSuperinterface( ParameterizedTypeName.get( ClassName.get( CommonMapper.class ),ClassName.get( sourceTypeMirror ),ClassName.get( targetTypeMirror ) ))
.build();
JavaFile javaFile = JavaFile.builder( packageName, typeSpec)
.build();
try {
javaFile.writeTo( filer );
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
}

private String getSimpleName(TypeMirror typeMirror){
String mirrorName = typeMirror.toString();
return mirrorName.substring( mirrorName.lastIndexOf( "." )+1 );
}

private String getPackageName(TypeMirror typeMirror){
String mirrorName = typeMirror.toString();
return mirrorName.substring( 0,mirrorName.lastIndexOf( "." ) );
}
}
创建BeanUtils工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class BeanUtils {

private static final Map<String,CommonMapper> commonMapperMap = new ConcurrentHashMap<>();

private static final ClassLoader CLASS_LOADER = BeanUtils.class.getClassLoader();

/**
* 根据source对象更新target对象
* @param source
* @param target
*/
public static void copyProperties(Object source, Object target){
String mapperClassName = getMapperClassName( source.getClass(), target.getClass() );
CommonMapper mapper = getMapper( mapperClassName );
mapper.to( source, target );
}

/**
* 创建T对象并赋值
* @param source
* @param targetClazz
* @return
* @param <T>
*/
public static <T> T copProperties(Object source,Class<T> targetClazz){
String mapperClassName = getMapperClassName( source.getClass(), targetClazz );
CommonMapper mapper = getMapper( mapperClassName );
return (T) mapper.to( source );
}

private static String getMapperClassName(Class source,Class target){
String sourceName = source.getSimpleName();
String targetName = target.getSimpleName();
String packageName = source.getPackageName();
String mapperClassName = packageName+"."+sourceName+"To"+targetName+"Mapper";
return mapperClassName;
}

/**
* 根据全限定类名获得对应的Mapper对象
* @param mapperClassName
* @return
*/
private static CommonMapper getMapper(String mapperClassName){
// 使用map缓存Mapper对象避免重复加载判断
CommonMapper commonMapper = commonMapperMap.computeIfAbsent( mapperClassName, (className) -> {
try {
Class<?> mapperClass = CLASS_LOADER.loadClass( mapperClassName );
CommonMapper mapper = (CommonMapper) Mappers.getMapper( mapperClass );
return mapper;
}
catch ( ClassNotFoundException e ) {
throw new RuntimeException( e );
}
} );
if ( commonMapper == null ){
throw new RuntimeException(mapperClassName+"不存在");
}
return commonMapper;
}
}

processor项目的代码就开发完成啦,最后在javax.annotation.processing.Processor文件里指定一下类名就好啦

1
2
// javax.annotation.processing.Processor
pers.orange.MappingProcessor

测试一下

在新建一个新的test项目,并引入刚刚的processor项目和MapStruct项目,同时指定注解处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>pers.orange</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
</properties>

<dependencies>
<dependency>
<groupId>pers.orange</groupId>
<artifactId>processor</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
// 需要注意处理器的加载顺序,需要processor项目先
<path>
<groupId>pers.orange</groupId>
<artifactId>processor</artifactId>
<version>1.0-SNAPSHOT</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    @Test
public void test01(){
Source source = new Source();
source.setId( 1 );
source.setName( "source" );
Target target = new Target();
BeanUtils.copyProperties( source, target );
System.out.println(target);
Target target1 = BeanUtils.copProperties( source, Target.class );
System.out.println(target1);
}

// 输出结果
Target{id=1, name='source'}
Target{id=1, name='source'}

可以看到对象映射成功~,同时在idea的target下也能很清楚的看到所生成的Java文件。

image-20220923143821299

image-20220923143836414

总结

通过这篇水文,相信大家也能看出编译期注解处理器APT的魅力,通过它我们可以在编译期自定义生成各式各样的代码。在Java开发中总是存在着很多重复的代码,例如CRUD等,我们可以尝试使用APT来自动生成简化我们的开发~。如果想要加深对APT的使用,可以去看看[MapStruct](mapstruct/mapstruct: An annotation processor for generating type-safe bean mappers (github.com))的源码。

参考资料