java注解-使用注解处理器实现动态生成get和set方法

一、简介

本文将介绍如何创建一个注解处理器实现 lombok 插件中的 @Data 功能,用过 @Data 注解的小伙伴都知道他会自动帮你创建所有字段的 get 和 set 方法。
项目地址:https://github.com/1277463718lmt/apt-demo.git

二、如何实现

1. 环境说明:

java1.8、idea、maven

2. 创建项目

项目中有两个模块,apt-test 主要使用来测试的,apt-tool 是实现注解处理器的。

apt-tool 模块 pom.xml 需要引入的依赖

    <dependencies>
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc2</version>
        </dependency>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>
    </dependencies>

apt-test 模块 pom.xml 需要引入的依赖

    <!--apt-tool 模块 -->
    <dependencies>
        <dependency>
            <artifactId>apt-tool</artifactId>
            <groupId>com.linmt</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
<span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.10.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

3. 定义 @Data 注解

@Retention 只需要定义成 RetentionPolicy.SOURCE,运行时不需要使用该注解。

package com.linmt.annotation;

import java.lang.annotation.*;

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

4. 定义 @Data 的注解处理器

创建一个注解处理器类 DataProcessor,继承 AbstractProcessor 类,记得在类上加上 @AutoService(Processor.class) 注解。

通过重写 process 方法实现动态修改 class 文件的内容。

其中需要用到 java 抽象语法树较为复杂,api 可参考网上资料。

https://blog.csdn.net/youanyyou/article/details/120582478

package com.linmt.processor;

import com.google.auto.service.AutoService;
import com.linmt.annotation.Data;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
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.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.LinkedHashSet;
import java.util.Set;

// 该注解省去了手动在 /resources/META-INF/services/ 下创建 javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
public class DataProcessor extends AbstractProcessor {
private Messager messager;
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private Names names;

<span class="hljs-comment">// 定义需要处理的注解</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-title class_">Set</span>&lt;<span class="hljs-title class_">String</span>&gt; <span class="hljs-title function_">getSupportedAnnotationTypes</span>(<span class="hljs-params"></span>) {
    <span class="hljs-title class_">Set</span>&lt;<span class="hljs-title class_">String</span>&gt; annotations = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedHashSet</span>&lt;&gt;();
    annotations.<span class="hljs-title function_">add</span>(<span class="hljs-title class_">Data</span>.<span class="hljs-property">class</span>.<span class="hljs-title function_">getCanonicalName</span>());
    <span class="hljs-keyword">return</span> annotations;
}

<span class="hljs-comment">// 版本支持</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-title class_">SourceVersion</span> <span class="hljs-title function_">getSupportedSourceVersion</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-title class_">SourceVersion</span>.<span class="hljs-title function_">latestSupported</span>();
}

<span class="hljs-comment">// 初始化</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> synchronized <span class="hljs-built_in">void</span> <span class="hljs-title function_">init</span>(<span class="hljs-params">ProcessingEnvironment processingEnv</span>) {
    <span class="hljs-variable language_">super</span>.<span class="hljs-title function_">init</span>(processingEnv);
    <span class="hljs-variable language_">this</span>.<span class="hljs-property">messager</span> = processingEnv.<span class="hljs-title function_">getMessager</span>();
    <span class="hljs-variable language_">this</span>.<span class="hljs-property">javacTrees</span> = <span class="hljs-title class_">JavacTrees</span>.<span class="hljs-title function_">instance</span>(processingEnv);
    <span class="hljs-title class_">Context</span> context = ((<span class="hljs-title class_">JavacProcessingEnvironment</span>) processingEnv).<span class="hljs-title function_">getContext</span>();
    <span class="hljs-variable language_">this</span>.<span class="hljs-property">treeMaker</span> = <span class="hljs-title class_">TreeMaker</span>.<span class="hljs-title function_">instance</span>(context);
    <span class="hljs-variable language_">this</span>.<span class="hljs-property">names</span> = <span class="hljs-title class_">Names</span>.<span class="hljs-title function_">instance</span>(context);

    <span class="hljs-title function_">log</span>(<span class="hljs-string">"初始化...."</span>);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-built_in">boolean</span> <span class="hljs-title function_">process</span>(<span class="hljs-params"><span class="hljs-built_in">Set</span>&lt;? <span class="hljs-keyword">extends</span> TypeElement&gt; annotations, RoundEnvironment roundEnv</span>) {
    <span class="hljs-title class_">Set</span>&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Element</span>&gt; elements = roundEnv.<span class="hljs-title function_">getElementsAnnotatedWith</span>(<span class="hljs-title class_">Data</span>.<span class="hljs-property">class</span>);
    <span class="hljs-keyword">for</span> (<span class="hljs-title class_">Element</span> element : elements) {
        <span class="hljs-title function_">log</span>(<span class="hljs-string">"当前类="</span> + element.<span class="hljs-title function_">getSimpleName</span>());
        <span class="hljs-title class_">JCTree</span> jcTree = javacTrees.<span class="hljs-title function_">getTree</span>(element);

        <span class="hljs-comment">// 以下这段代码解决报错java.lang.AssertionError: Value of x -1</span>
        treeMaker.<span class="hljs-property">pos</span> = jcTree.<span class="hljs-property">pos</span>;
        jcTree.<span class="hljs-title function_">accept</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeTranslator</span>() {
            <span class="hljs-meta">@Override</span>
            <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-title function_">visitClassDef</span>(<span class="hljs-params">JCTree.JCClassDecl jcClassDecl</span>) {
                <span class="hljs-title class_">List</span>&lt;<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>&gt; jcVariableDeclList = <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>();

                <span class="hljs-comment">// jcClassDecl.defs为类里面的所有定义</span>
                <span class="hljs-keyword">for</span> (<span class="hljs-title class_">JCTree</span> tree : jcClassDecl.<span class="hljs-property">defs</span>) {
                    <span class="hljs-comment">// 判断是变量</span>
                    <span class="hljs-keyword">if</span> (tree.<span class="hljs-title function_">getKind</span>().<span class="hljs-title function_">equals</span>(<span class="hljs-title class_">Tree</span>.<span class="hljs-property">Kind</span>.<span class="hljs-property">VARIABLE</span>)) {
                        <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span> jcVariableDecl = (<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>) tree;
                        <span class="hljs-comment">// 此处能用com.sun.tools.javac.util.List的add方法</span>
                        jcVariableDeclList = jcVariableDeclList.<span class="hljs-title function_">append</span>(jcVariableDecl);
                        <span class="hljs-title function_">log</span>(<span class="hljs-string">"已读取字段="</span> + jcVariableDecl.<span class="hljs-title function_">getName</span>().<span class="hljs-title function_">toString</span>());
                    }
                }

                <span class="hljs-keyword">for</span> (<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span> jcVariableDecl : jcVariableDeclList) {
                    <span class="hljs-comment">// 追加类里面的定义</span>
                    jcClassDecl.<span class="hljs-property">defs</span> = jcClassDecl.<span class="hljs-property">defs</span>.<span class="hljs-title function_">append</span>(<span class="hljs-title function_">buildGetterMethod</span>(jcVariableDecl));
                    jcClassDecl.<span class="hljs-property">defs</span> = jcClassDecl.<span class="hljs-property">defs</span>.<span class="hljs-title function_">append</span>(<span class="hljs-title function_">buildSetterMethod</span>(jcVariableDecl));
                }
                <span class="hljs-variable language_">super</span>.<span class="hljs-title function_">visitClassDef</span>(jcClassDecl);
            }
        });
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/***
 * 构建get方法的JCTree.JCMethodDecl对象
 * <span class="hljs-doctag">@param</span> <span class="hljs-variable">jcVariableDecl</span>
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> <span class="hljs-title function_">buildGetterMethod</span>(<span class="hljs-params">JCTree.JCVariableDecl jcVariableDecl</span>) {
    <span class="hljs-comment">// 构建方法体</span>
    <span class="hljs-title class_">ListBuffer</span>&lt;<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCStatement</span>&gt; statements = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ListBuffer</span>&lt;&gt;();
    statements.<span class="hljs-title function_">append</span>(treeMaker.<span class="hljs-title class_">Return</span>(treeMaker.<span class="hljs-title class_">Select</span>(treeMaker.<span class="hljs-title class_">Ident</span>(names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"this"</span>)), jcVariableDecl.<span class="hljs-title function_">getName</span>())));
    <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCBlock</span> body = treeMaker.<span class="hljs-title class_">Block</span>(<span class="hljs-number">0</span>, statements.<span class="hljs-title function_">toList</span>());

    <span class="hljs-comment">// JCTree.JCMethodDecl不能直接使用new关键字来创建,可以通过treeMaker.MethodDef来创建</span>
    <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> jcMethodDecl = treeMaker.<span class="hljs-title class_">MethodDef</span>(
            treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PUBLIC</span>), <span class="hljs-comment">// 访问标志</span>
            <span class="hljs-title function_">generateGetMethodName</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>()), <span class="hljs-comment">// 方法名</span>
            jcVariableDecl.<span class="hljs-property">vartype</span>, <span class="hljs-comment">// 返回参数</span>
            <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 泛型参数列表</span>
            <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 参数列表</span>
            <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 异常声明列表</span>
            body, <span class="hljs-comment">// 方法体</span>
            <span class="hljs-literal">null</span>
    );

    <span class="hljs-keyword">return</span> jcMethodDecl;
}

<span class="hljs-comment">/***
 * 构建set方法的JCTree.JCMethodDecl对象
 * <span class="hljs-doctag">@param</span> <span class="hljs-variable">jcVariableDecl</span>
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> <span class="hljs-title function_">buildSetterMethod</span>(<span class="hljs-params">JCTree.JCVariableDecl jcVariableDecl</span>) {
    <span class="hljs-comment">// 构建方法体</span>
    <span class="hljs-title class_">ListBuffer</span>&lt;<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCStatement</span>&gt; statements = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ListBuffer</span>&lt;&gt;();
    statements.<span class="hljs-title function_">append</span>(treeMaker.<span class="hljs-title class_">Exec</span>(
            <span class="hljs-comment">// this.var = var;</span>
            <span class="hljs-comment">// treeMaker.Assign 赋值语句</span>
            treeMaker.<span class="hljs-title class_">Assign</span>(
                    treeMaker.<span class="hljs-title class_">Select</span>(treeMaker.<span class="hljs-title class_">Ident</span>(names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"this"</span>)), jcVariableDecl.<span class="hljs-title function_">getName</span>()),
                    treeMaker.<span class="hljs-title class_">Ident</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>())
            )
    ));
    <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCBlock</span> body = treeMaker.<span class="hljs-title class_">Block</span>(<span class="hljs-number">0</span>, statements.<span class="hljs-title function_">toList</span>());

    <span class="hljs-title class_">List</span>&lt;<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>&gt; params = <span class="hljs-title class_">List</span>.<span class="hljs-title function_">of</span>(
            treeMaker.<span class="hljs-title class_">VarDef</span>(
                    treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PARAMETER</span>, <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>()),
                    jcVariableDecl.<span class="hljs-property">name</span>, <span class="hljs-comment">// 参数名</span>
                    jcVariableDecl.<span class="hljs-property">vartype</span>, <span class="hljs-comment">// 类型</span>
                    <span class="hljs-literal">null</span> <span class="hljs-comment">// 初始值</span>
            )
    );

    <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> jcMethodDecl = treeMaker.<span class="hljs-title class_">MethodDef</span>(
            treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PUBLIC</span>), <span class="hljs-comment">// 访问标志</span>
            <span class="hljs-title function_">generateSetMethodName</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>()), <span class="hljs-comment">// 方法名 = setVar</span>
            treeMaker.<span class="hljs-title class_">Type</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Type</span>.<span class="hljs-title class_">JCVoidType</span>()), <span class="hljs-comment">// 返回参数=void</span>
            <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 泛型参数列表</span>
            params, <span class="hljs-comment">// 参数列表</span>
            <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 异常声明列表</span>
            body, <span class="hljs-comment">// 方法体</span>
            <span class="hljs-literal">null</span>
    );

    <span class="hljs-keyword">return</span> jcMethodDecl;
}

<span class="hljs-comment">/**
 * 生成get方法的名称
 *
 * <span class="hljs-doctag">@param</span> <span class="hljs-variable">name</span>
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">Name</span> <span class="hljs-title function_">generateGetMethodName</span>(<span class="hljs-params">Name name</span>) {
    <span class="hljs-title class_">String</span> s = name.<span class="hljs-title function_">toString</span>();
    <span class="hljs-keyword">return</span> names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"get"</span> + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>).<span class="hljs-title function_">toUpperCase</span>() + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">1</span>, name.<span class="hljs-title function_">length</span>()));
}

<span class="hljs-comment">/**
 * 生成set方法的名称
 *
 * <span class="hljs-doctag">@param</span> <span class="hljs-variable">name</span>
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">Name</span> <span class="hljs-title function_">generateSetMethodName</span>(<span class="hljs-params">Name name</span>) {
    <span class="hljs-title class_">String</span> s = name.<span class="hljs-title function_">toString</span>();
    <span class="hljs-keyword">return</span> names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"set"</span> + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>).<span class="hljs-title function_">toUpperCase</span>() + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">1</span>, name.<span class="hljs-title function_">length</span>()));
}


<span class="hljs-comment">/**
 * 打印日志
 *
 * <span class="hljs-doctag">@param</span> <span class="hljs-variable">msg</span>
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-built_in">void</span> <span class="hljs-title function_">log</span>(<span class="hljs-params"><span class="hljs-built_in">String</span> msg</span>) {
    messager.<span class="hljs-title function_">printMessage</span>(<span class="hljs-title class_">Diagnostic</span>.<span class="hljs-property">Kind</span>.<span class="hljs-property">NOTE</span>, msg);
}

}

5. 创建一个测试类

在 apt-test 下创建一个 User 类

package com.linmt.po;

import com.linmt.annotation.Data;

@Data
public class User {
private Long id;
private String name;
}

6. 通过 idea 的 maven 工具栏进行编译

点击 clean 清除编译的生成的文件,点击 compile 进行编译。

如果想要在 idea 中去断点调试可以右键 compile, 点击 debug

7. 查看编译后的结果

生成的 class 文件在 apt-test 下的 target/classes/ 下

8. 通过反射查询 User 类的方法

public class Main {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    Method[] declaredMethods = User.class.getDeclaredMethods();
    <span class="hljs-keyword">for</span> (Method method: declaredMethods) {
        System.out.println(method.getName());
    }
}

}

//

打印的结果

setId
getName
getId
setName

三、遇到的问题

1. 编译报错一

解决方法:DataProcessor 类中增加一行代码

2. 编译报错二

解决方法:idea 中的 compiler 中配置参数 -Djps.track.ap.dependencies=false