[Java基础] 1、APT实现注解处理器

自定义编译时注解处理器

使用IDEA构建的maven项目。一个生产者,一个消费者。

github地址

注解的保留类型

@Retention(RetentionPolicy.SOURCE)
.java源文件有,编译成class文件后不保存的注解,运行时也不能获取

@Retention(RetentionPolicy.CLASS)
编译后的class文件中有,但在运行时不能获取到。

@Retention(RetentionPolicy.RUNTIME)
可以在运行时获取到,即运行时注解。基于反射使用

APT简介

APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类,关于AbstractProcessor类后面会做详细说明。

自定义注解处理器

创建一个注解

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
    
    ElementType.TYPE})
public @interface Data {
    
    

}

实现注解处理器

其中具体如何添加get,set方法代码原理可以看下面这篇文章。

Java中的屠龙之术——如何修改语法树

然后继承AbstractProcessor类

@SupportedAnnotationTypes("cn.wen233.demo.processor.Data")
public class DataProcessor extends AbstractProcessor {
    
    

    // 编译时期输入日志的
    private Messager messager;
    // 提供了待处理的抽象语法树
    private JavacTrees javacTrees;
    // 封装了创建AST节点的一些方法
    private TreeMaker treeMaker;
    // 提供了创建标识符的方法
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    
    
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.javacTrees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        // 获取所有带有Data注解的元素,可能是类,方法,字段等
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Data.class);
        // 对其进行遍历
        elementsAnnotatedWith.forEach(e -> {
    
    
            JCTree tree = javacTrees.getTree(e);
            // 添加访问器
            tree.accept(new TreeTranslator() {
    
    
                /**
                 * 如果该元素是类则执行该方法
                 * @param jcClassDecl 类定义
                 */
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
    
    
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    // 获取类中所有属性字段
                    for (JCTree jcTree : jcClassDecl.defs) {
    
    
                        if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
    
    
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
                    // 对于变量进行生成方法的操作
                    jcVariableDeclList.forEach(jcVariableDecl -> {
    
    
                        messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + elementsAnnotatedWith.toString());

        return true;
    }

    private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
    
    
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        // 生成表达式 例如 this.a = a;
        JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(
                names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
        statements.append(aThis);
        JCTree.JCBlock block = treeMaker.Block(0, statements.toList());

        // 生成形参
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
                jcVariableDecl.getName(), jcVariableDecl.vartype, null);
        List<JCTree.JCVariableDecl> parameters = List.of(param);

        // 生成返回对象
        JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
                getNewMethodName(jcVariableDecl.getName(), "set"),
                methodType,
                List.nil(),
                parameters,
                List.nil(),
                block,
                null);
    }
    
    private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
    
    
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(
                names.fromString("this")), jcVariableDecl.getName())));
        JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
                getNewMethodName(jcVariableDecl.getName(), "get"),
                jcVariableDecl.vartype,
                List.nil(),
                List.nil(),
                List.nil(),
                block,
                null);
    }

    private Name getNewMethodName(Name name, String flag) {
    
    
        String s = name.toString();
        return names.fromString(flag + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

    private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
    
    
        return treeMaker.Exec(
                treeMaker.Assign(
                        lhs,
                        rhs
                )
        );
    }
}

添加tools.jar

因为JCTree这些类以及实现在jdk自带的tools.jar包中,所以需要将tools.jar导入到项目中。

之前看的都是导入本地tools.jar路径到项目中,但这样如果项目到别的电脑上就没有tools.jar。

所以我把tools.jar复制了一份到resources中,并将其作为maven依赖导入项目。

如图
在这里插入图片描述

<!-- 添加tool-->
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.5.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/tools.jar</systemPath>
        </dependency>

添加SPI

在resources目录下添加META-INF目录
META-INF目录中添加services目录,
目录中新建一个javax.annotation.processing.Processor文件,
文件中放置cn.wen233.demo.processor.DataProcessor
如下图
在这里插入图片描述

maven打包

最后在maven打包时候又报错

错误: 服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider cn.wen233.demo.processor.DataProcessor not found时抛出异常错误

好像是因为项目编译的时候会使用注解处理器,此时我们自定义的注解处理器还未编译,但就已经需要使用,便报错没有找到。

最后在pom.xml中添加

<build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- Disable annotation processing for ourselves. -->
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </build>

最后的pom.xml文件

<?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>org.example</groupId>
    <artifactId>demo-annotation-proessor</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- Disable annotation processing for ourselves. -->
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- 添加tool-->
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.5.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

</project>

至此就可以获得一个我们自定义的注解处理器了。

使用自定义的注解处理器

新建项目

新建一个maven项目,然后将之前的注解处理器的那个jar包作为一个maven依赖导入项目。并通过配置build在项目编译时添加注解处理器。

新建项目maven的pom.xml

<?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>org.example</groupId>
    <artifactId>demo-test1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>demo-annotation-proessor</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/demo-annotation-proessor-1.0-SNAPSHOT.jar</systemPath>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <annotationProcessors>
                        <annotationProcessor>
                            cn.wen233.demo.processor.DataProcessor
                        </annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

IDEA设置

设置启用注解处理器
在这里插入图片描述
最后设置好了之后的情况。
在这里插入图片描述

在这里插入图片描述

遇到的问题

1、使用自定义注解处理器时报错Error:java: Compilation failed: internal java compiler error
有可能是因为编译时需要使用注解处理器,但自定义的注解处理器尚未加载。可以配置IDEAAnnotation Processing;也可以不先去掉注解,然后构建一次项目。

2、未找到ExceptionCode,需要导入
import com.wen233.processor.ExceptionCode;

3、报错信息 java: -source 1.5 中不支持 diamond 运算符
在maven pom.xml中添加

<plugin>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.7.0</version>
                 <configuration>
                     <source>1.8</source>
                     <target>1.8</target>
                     ……

猜你喜欢

转载自blog.csdn.net/qq_43621091/article/details/107080827