使用AbstractProcessor编译期生成get、set代码

91 阅读2分钟

Maven依赖

        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8.0</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc7</version>
            <scope>provided</scope>
        </dependency>

自定义注解 GetSetAnnotation

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 注解在编译器生效
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GetSetAnnotation {

}

注解处理器 GetSetAnnotationProcessor

package com.example;

import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * 使用AST,生成对应的getter,setter方法
 */
// 限定注解类型
@SupportedAnnotationTypes("com.example.GetSetAnnotation")
// 限定java版本号
@SupportedSourceVersion(SourceVersion.RELEASE_8)
// 借用SPI机制
@AutoService(Processor.class)
public class GetSetAnnotationProcessor extends AbstractProcessor {

    private JavacTrees trees;
    private TreeMaker treeMaker;
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 获取抽象语法树实例
        trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        treeMaker = TreeMaker.instance(context);
        names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(GetSetAnnotation.class)) {
            if (element.getKind() == ElementKind.CLASS) {
                // 获取类声明对象
                JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) trees.getTree(element);
                // 循环遍历类中所有封闭元素,包括字段、方法等
                for (Element enclosed : element.getEnclosedElements()) {
                    // 若是私有字段,则生成getter,setter代码
                    if (enclosed.getKind() == ElementKind.FIELD && enclosed.getModifiers().contains(Modifier.PRIVATE)) {
                        // 获取字段声明
                        JCTree.JCVariableDecl fieldDecl = (JCTree.JCVariableDecl) trees.getTree(enclosed);
                        // classDecl.defs 表示 类的所有声明,包括字段、方法、初始化块等
                        classDecl.defs = classDecl.defs.append(makeGetterMethod(fieldDecl));
                        classDecl.defs = classDecl.defs.append(makeSetterMethod(fieldDecl));
                    }
                }
            } else {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with @GenerateGetterSetter", element);
            }
        }
        return true;
    }
    
    /**
     * 创建getter方法语法树
     * @param field
     * @return
     */
    private JCTree.JCMethodDecl makeGetterMethod(JCTree.JCVariableDecl field) {
        Name fieldName = field.getName();
        // 方法名
        String getterName = "get" + capitalize(fieldName.toString());
        // 返回类型
        JCTree.JCExpression returnType = field.vartype;
        // 方法体
        JCTree.JCBlock body = treeMaker.Block(0, List.of(
                treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), fieldName))
        ));

        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString(getterName),
                returnType,
                List.nil(),
                List.nil(),
                List.nil(),
                body,
                null
        );
    }

    private JCTree.JCMethodDecl makeSetterMethod(JCTree.JCVariableDecl field) {
        Name fieldName = field.getName();
        String setterName = "set" + capitalize(fieldName.toString());
        JCTree.JCExpression paramType = field.vartype;

        JCTree.JCVariableDecl param = treeMaker.VarDef(
                treeMaker.Modifiers(Flags.PARAMETER),
                fieldName,
                paramType,
                null
        );

        JCTree.JCBlock body = treeMaker.Block(0, List.of(
                treeMaker.Exec(
                        treeMaker.Assign(
                                treeMaker.Select(treeMaker.Ident(names.fromString("this")), fieldName),
                                treeMaker.Ident(fieldName)
                        )
                )
        ));

        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString(setterName),
                treeMaker.TypeIdent(com.sun.tools.javac.code.TypeTag.VOID),
                List.nil(),
                List.of(param),
                List.nil(),
                body,
                null
        );
    }

    private String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

编译测试

项目打包

mvn clean package

测试类

package com.example;

@GetSetAnnotation
public class People {

    private String name;

    private int age;

}

编译命令

javac -cp target/aptexample-1.0-SNAPSHOT.jar -processor com.example.GetSetAnnotationProcessor  src/main/java/com/example/People.java

编译结果

package com.example;

public class People {
    private String name;
    private int age;

    public People() {
    }

    public String getName() {
        return this.name;
    }

    public void setName(String var1) {
        this.name = var1;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int var1) {
        this.age = var1;
    }
}

使用IDEA REMOTE JVM DEBUG 功能debug调试

创建 REMOTE JVM DEBUG

复制远程连接启动命令-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=55411 image.png

代码编译命令

# suspend=y 表示启动时暂停,等待远程连接
javac -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=55411 -cp target/aptexample-1.0-SNAPSHOT.jar -processor com.example.GetSetAnnotationProcessor src/main/java/com/example/People.java

image.png

启动 REMOTE JVM DEBUG,进行调试

image.png