APT介绍
在JDK6的时候引入了JSR269的标准,即 Pluggable Annotation Processing API (可插入注解处理API),简称APT。它提供了在编译时期改变一些行为的相关API,例如生成一些新的JAVA文件。
大致处理流程如下图,APT在编译成class文件时执行,在这个过程中可以自定义相关行为,例如生成新的源文件(MapStruct),修改AST语法树(Lombok),直到所有编译处理器都没有在对JAVA文件进行修改为止,才会走到后续的生成Class文件的流程。
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文件。
总结
通过这篇水文,相信大家也能看出编译期注解处理器APT
的魅力,通过它我们可以在编译期自定义生成各式各样的代码。在Java开发中总是存在着很多重复的代码,例如CRUD等,我们可以尝试使用APT来自动生成简化我们的开发~。如果想要加深对APT的使用,可以去看看[MapStruct](mapstruct/mapstruct: An annotation processor for generating type-safe bean mappers (github.com))的源码。
参考资料