Java 代码自动化生成之 APT
Java 代码自动化生成之 APT
在 Java
中,有多种方式可以实现自动化生成代码的目的。以下是几种常见的方式:
-
注解处理器(Annotation Processor):使用注解处理器可以在编译期间根据注解生成额外的代码。注解处理器可以扫描源代码中的注解,提取注解信息,并根据注解生成新的
Java
代码。 -
模板引擎(Template Engine):模板引擎是一种将模板文件与数据进行组合,生成最终代码的工具。开发者可以定义模板文件,其中包含占位符或特定语法,然后使用模板引擎将模板文件与实际数据进行合并,生成最终的代码文件。常见的
Java
模板引擎包括FreeMarker
、Thymeleaf
等。 -
字节码操作库(Bytecode Manipulation Libraries):字节码操作库允许开发者直接操作
Java
字节码,修改现有类的字节码或生成新的类。通过字节码操作库,开发者可以在编译时动态地生成和修改Java
类。常见的字节码操作库包括ASM
、Byte Buddy
和Javassist
等。 -
反射(Reflection):反射是
Java
语言提供的一种机制,允许在运行时获取和操作类的信息。通过反射,开发者可以动态地创建对象、调用方法、访问字段等。利用反射,开发者可以编写通用的代码生成逻辑,根据需要生成特定的代码。然而,相比于前述的方式,反射通常会带来较高的性能开销。
这里主要介绍一下APT
技术。
APT
Java
在Java5
引入注解之后,又在之后引入APT(Annotation Processing Tool)
,这是一个处理注解的工具库,支持我们在代码编译期间处理注解,通过自定义注解处理器对注解进行处理,生成额外的代码或进行其他操作。
在Java7
中APT(API)
被废弃,提供了新的API
来进行注解处理即注解处理器Annotation Processor
(这个文档)。
不过,一般情况下我们还是叫这种技术为APT
。
原理
它的工作原理大概是这样的:
-
注解解析:编译器在编译过程中会检测源代码中的注解,并将这些注解信息传递给
APT
。 -
注解处理器加载:编译器会加载项目中配置的注解处理器(
Annotation Processor
)。注解处理器是开发者编写的特殊类,它实现了javax.annotation.processing.Processor
接口或继承了javax.annotation.processing.AbstractProcessor
类。 -
注解处理器执行:
APT
调用加载的注解处理器的相应方法(如process()
方法),传递注解和元素信息给处理器,生成新的Java
源代码或者其他文件,或执行其他相关的操作。 -
编译输出:生成的
Java
代码编译成class
文件,和其他class
文件一起输出。
一个 APT 项目都需要什么
- 自定义注解
- 注解处理器
- 配置文件
目录结构是这样的:
- src/
- main/
- java/
- com/
- example/
- annotations/
- MyAnnotation.java # 自定义注解类--示意,非项目真实文件名
- processors/
- MyAnnotationProcessor.java # 注解处理器类
- resources/ #资源目录
- META-INF/
- services/
- javax.annotation.processing.Processor # Processor服务注册文件
- pom.xml # (Maven项目的配置文件,如果使用Maven构建
- build.gradle # Gradle项目的配置文件,如果使用Gradle构建
- ...
pom
<?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>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<!-- 自动生成生成Processor服务注册文件的库 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
<!-- https://github.com/square/javapoet 用来创建.java文件的 Java Api 库 -->
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>8</source>
<target>8</target>
<compilerArgument>-proc:none</compilerArgument> <!-- 禁用注解处理器 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
自定义注解
这个自定义注解就是我们需要给其他代码使用的注解,APT 根据这个包含注解的类自动生成新的代码类(Processor
里面操作)。
一般情况我们把这些自定义注解写在单独的 module
中。因为,在使用中,我们需要在代码中引用到这些注解,但是不需要引用 Processor
,所以,可以将他们分别写到独立的 module
中。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface AutoMapper {
String name() default "";
Class<?> baseMapper() default Void.class;
}
Processor
Processor
是真正处理注解和生成代码的地方。需要继承 AbstractProcessor
类。
// 会帮我们自动生成javax.annotation.processing.Processor文件
com.google.auto.service.AutoService(Processor.class)
@SupportedAnnotationTypes({
"xxx.xxx.AutoMapper" // 指定我们这个注解处理器能够处理的注解,写我们想要处理的注解
})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class AutoGeneratorProcessor extends AbstractProcessor {
private Filer filer;
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
// 处理注解的入口方法,在这里获取注解的数据,然后,生成代码等
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) {
return false;
}
parseElement(roundEnvironment, AutoMapper.class);
return true;
}
private void parseElement(RoundEnvironment roundEnvironment, Class<? extends Annotation> annotationClass) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotationClass);
for (Element element : elements) {
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
String className = element.getSimpleName().toString();
ClassName elementClass = ClassName.get(packageName, className);
mapperProcess(element, packageName, className, elementClass);
}
}
private void mapperProcess(Element element, String packageName, String className, ClassName elementClass) {
AutoMapper autoMapper = element.getAnnotation(AutoMapper.class);
// mapper 类名
String name = (autoMapper.name() == null || autoMapper.name().length() == 0) ? className + "Mapper" : autoMapper.name();
TypeMirror baseMapperTypeMirror = readValue(element, AutoMapper.class, "baseMapper");
// 处理父类BaseMapper
ParameterizedTypeName baseMapperType = null;
if (baseMapperTypeMirror != null && !Objects.equals(baseMapperTypeMirror.toString(), Void.class.getName())) {
baseMapperType = ParameterizedTypeName.get((ClassName) ClassName.get(baseMapperTypeMirror), elementClass);
}
// 构建需要生成的java类
TypeSpec.Builder builder = TypeSpec.interfaceBuilder(name).addModifiers(Modifier.PUBLIC);
if(baseMapperType != null){
builder.addSuperinterface(baseMapperType);
}
JavaFile javaFile = JavaFile.builder(packageName + ".mapper", builder.build()).build();
try {
// 生成类文件
javaFile.writeTo(filer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
其中几个重要的类:
ProcessingEnvironment
定义了许多方法和属性,用于获取有关编译环境的信息:
getElementUtils()
:返回一个Elements
对象,用于处理元素相关的操作,如获取元素的注解、父类、接口等。getTypeUtils()
:返回一个Types
对象,用于处理类型相关的操作,如获取类型的父类、接口、类型转换等。getFiler()
:返回一个Filer
对象,用于生成新的源文件、类文件或其他文件。getMessager()
:返回一个 Messager 对象,用于在编译过程中输出消息、警告和错误。getOptions()
:返回一个Map<String, String>
,用于获取注解处理器的选项参数。
Element
Element
接口表示源代码中的一个元素,可以是类、接口、方法、字段、参数等。它提供了许多方法和属性,用于获取有关该元素的信息,如名称、修饰符、注解、类型等。一些常用的方法和属性包括:
getKind()
:返回该元素的类型,如CLASS
、METHOD
、FIELD
等。getSimpleName()
:返回该元素的简单名称,即不包含包名的名称。getModifiers()
:返回该元素的修饰符集合,如PUBLIC
、PRIVAT
E 等。getAnnotation(Class<A> annotationType)
:返回与给定注解类型匹配的注解对象。getEnclosingElement()
:返回包含该元素的父元素,如类中的方法、内部类中的字段等。asType()
:返回该元素的类型,即一个TypeMirror
对象。
通过使用 Element
接口提供的方法和属性,注解处理器可以获取有关源代码中元素的详细信息,并根据需要进行相应的处理。例如,可以获取元素的注解,检查其修饰符,获取其类型等。
获取注解的属性
这里主要说一下返回值是Class
的属性,直接通过element.getAnnotation()
访问属性会有异常:
...Attempt to access Class object for TypeMirror...
不能够直接获取。需要处理之后才能访问(下面 readValue
方法)。
private AnnotationMirror getEventTypeAnnotationMirror(Element element, Class<?> clazz) {
String clazzName = clazz.getName();
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
if (Objects.equals(annotationMirror.getAnnotationType().toString(), clazzName)) {
return annotationMirror;
}
}
return null;
}
private AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
if (Objects.equals(key, entry.getKey().getSimpleName().toString())) {
return entry.getValue();
}
}
return null;
}
private <T> T readValue(Element element, Class<?> clazz, String key) {
AnnotationMirror am = getEventTypeAnnotationMirror(element, clazz);
AnnotationValue av = null;
if (am != null) {
av = getAnnotationValue(am, key);
}
return av == null ? null : (T) av.getValue();
}
// private TypeMirror readTypeMirrorValue(Element element, Class<?> clazz, String key) {
// AnnotationMirror am = getEventTypeAnnotationMirror(element, clazz);
// AnnotationValue av = null;
// if (am != null) {
// av = getAnnotationValue(am, key);
// }
// return av == null ? null : (TypeMirror) av.getValue();
// }
这一块可以参考:
使用
maven
项目的话,依赖这个项目就可以了。
pom.xml:
<dependencies>
<!-- 上面的processor项目 -->
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
gradle
项目的话,也是类似。
build.gradle:
dependencies {
// 上面的processor项目
annotationProcessor project(':xxx')
}
当然,如果注解是单独项目的话,还需要引用注解所在的项目。
转载自:https://juejin.cn/post/7233808084085424165