likes
comments
collection
share

Java 代码自动化生成之 APT

作者站长头像
站长
· 阅读数 11

Java 代码自动化生成之 APT

Java 中,有多种方式可以实现自动化生成代码的目的。以下是几种常见的方式:

  • 注解处理器(Annotation Processor):使用注解处理器可以在编译期间根据注解生成额外的代码。注解处理器可以扫描源代码中的注解,提取注解信息,并根据注解生成新的 Java 代码。

  • 模板引擎(Template Engine):模板引擎是一种将模板文件与数据进行组合,生成最终代码的工具。开发者可以定义模板文件,其中包含占位符或特定语法,然后使用模板引擎将模板文件与实际数据进行合并,生成最终的代码文件。常见的 Java 模板引擎包括 FreeMarkerThymeleaf 等。

  • 字节码操作库(Bytecode Manipulation Libraries):字节码操作库允许开发者直接操作 Java 字节码,修改现有类的字节码或生成新的类。通过字节码操作库,开发者可以在编译时动态地生成和修改 Java 类。常见的字节码操作库包括 ASMByte BuddyJavassist 等。

  • 反射(Reflection):反射是 Java 语言提供的一种机制,允许在运行时获取和操作类的信息。通过反射,开发者可以动态地创建对象、调用方法、访问字段等。利用反射,开发者可以编写通用的代码生成逻辑,根据需要生成特定的代码。然而,相比于前述的方式,反射通常会带来较高的性能开销。

这里主要介绍一下APT技术。

APT

JavaJava5引入注解之后,又在之后引入APT(Annotation Processing Tool),这是一个处理注解的工具库,支持我们在代码编译期间处理注解,通过自定义注解处理器对注解进行处理,生成额外的代码或进行其他操作。

Java7APT(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():返回该元素的类型,如 CLASSMETHODFIELD 等。
  • getSimpleName():返回该元素的简单名称,即不包含包名的名称。
  • getModifiers():返回该元素的修饰符集合,如 PUBLICPRIVATE 等。
  • 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();
// }

这一块可以参考:

  1. Getting Class annotation value
  2. Android 注解处理器中的 MirroredTypeException 异常处理

使用

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')
}

当然,如果注解是单独项目的话,还需要引用注解所在的项目。