java实现编译期自定义注解,自动生成set和get方法

目的:自定义一个@Data注解,实现效果给java类文件编译时为属性自动生成set和get方法

实现分为3步
1.idea新建一个空项目(maven)
2.新建两个java文件,一个是注解,一个是这个注解处理器
3.编译测试

1.idea新建一个maven的空项目

idea->file->new->project->maven->next->finsh
空项目是为了学习过程中没有其他额外的东西干扰你,你只需要关注在你学习的东西上

2.新建两个java文件

2.1:第一个文件是注解文件,Data.java
代码如下:

package cn.gyd.annotation.source;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
}

说明:
pubulic @interface 是java自定义注解的语法,后面“Data”是注解名,你可以随意起名
@Target(ElementType.TYPE) 标记该注解是在什么地方使用。这里TYPE表示接口、类、枚举
@Retention(RetentionPolicy.Source)标记该注解是在什么时候使用。Source表示编译器间。

2.2:第二个文件是注解处理器,DataProcessor.java
代码如下:

package cn.gyd.annotation.source.core;

import cn.gyd.annotation.source.Data;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeTranslator;

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

@SupportedAnnotationTypes("cn.gyd.annotation.source.Data")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class DataAnnotationProcessor extends AbstractProcessor{
    private JavacTrees javacTrees;
    private DataProcessor dataProcessor;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        javacTrees = JavacTrees.instance(processingEnv);
        dataProcessor = new DataProcessor(processingEnv);
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        Set set = roundEnv.getElementsAnnotatedWith(Data.class);
        for (Element element : set) {
            // 获取当前类的抽象语法树
            JCTree tree = javacTrees.getTree(element);
            // 获取抽象语法树的所有节点
            // Visitor 抽象内部类,内部定义了访问各种语法节点的方法
            tree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    jcClassDecl.defs.stream()
                            // 过滤,只处理变量类型
                            .filter(it -> it.getKind().equals(Tree.Kind.VARIABLE))
                            // 类型强转
                            .map(it -> (JCTree.JCVariableDecl) it)
                            .forEach(it -> {
                                // 添加get方法
                                jcClassDecl.defs = jcClassDecl.defs.prepend(dataProcessor.genGetterMethod(it));
                                // 添加set方法
                                jcClassDecl.defs = jcClassDecl.defs.prepend(dataProcessor.genSetterMethod(it));
                            });

                    super.visitClassDef(jcClassDecl);
                }
            });
        }
        return false;
    }
}

说明:
定义好注解,它还不能用,你需要赋予这个注解的职责或者说作用,我们称为它“注解处理器”,同时你需要告诉编译器执行这个“注解处理器”。那么怎么告诉编译器呢?那就是继承 extends AbstractProcessor,并定义该处理器是处理哪些注解的:@SupportedAnnotationTypes("cn.gyd.annotation.source.Data")
@AutoService(Processor.class)是google的第三方插件,作用是生成META-INF/services/javax.annotation.processing.processor文件,该文件的目的是告诉编译器谁继承了AbstractProcessor,编译器会自动执行继承类里的process()方法
处理器的作用代码不多做讲解主要是利用语法树对class文件进行代码的插入。这里要注意的是并不是对.java的源文件进行重写,而是在转成class字节码时进行的过程介入。
dataProcessor.genGetterMethod和dataProcessor.genSetterMethod方法请下方的源码自行下载获取

3.编译测试

新建User.java类(可以放入test的目录),代码如下:
package cn.gyd.annotation.source;

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

}
注意了,在新建User.java的时候请先完成以上两个文件的编译,即已生成对应的class文件,然后在单独编译该文件。单独编译可以使用idea右键文件选择“Recompile User.java”,你也可以使用javac命令行的模式单独编译。为什么要先编译注解文件和注解处理器,因为当你使用@Data这个注解的时候编译器检测到后就会去调用这个注解的处理器(DataProcessor.class),这个时候注解处理器是要以class文件的姿态去执行的。而如果注解处理器没有class则需要先编译,而编译必定触发注解处理器,结果就会导致死循环。
按顺序完成编译后请去target/classes文件夹下找到user.class文件看是否有如下代码:
public class User {
private Long id;
private String name;

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

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

public void setId(Long id) {
    this.id = id;
}

public Long getId() {
    return this.id;
}

public User() {
}

}

最后贴上源码下载地址:https://gitee.com/terryge/annotation,可以直接下载查看,有助于大家的理解

你可能感兴趣的:(技术研究,java)