初探Java 注解处理器 (Annotation Processor)

引言

自从Java 引入了注解(Annotation) 的特性以后,我们获得了由它带来的便利,尤其是在Spring当中得到了大量的应用。大部分情况下使用的注解都是运行时通过反射机制来使用它,今天我们不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。我们可以自定义注解,并注册相应的注解处理器来完成相应的操作,比如大家非常熟悉的lombok就是采用该机制实现的。

AbstractProcessor

每一个处理器都是继承于AbstractProcessor,如下所示:

public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    @Override
    public SourceVersion getSupportedSourceVersion() { }

}
  • init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, TypesFiler
  • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。在这里写我们的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让我们查询出包含特定注解的被注解元素。
  • getSupportedAnnotationTypes(): 这里指定这个注解处理器是处理哪些注解的。注意,它的返回值是一个字符串的集合。
  • getSupportedSourceVersion(): 用来指定我们使用的Java版本。通常这里返回SourceVersion.latestSupported()

在Java 7以后,我们也可以使用注解来代替getSupportedAnnotationTypes()getSupportedSourceVersion(),像这样:

@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
   // 合法注解全名的集合
 })
public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}

注册处理器

我们可能会问如何让处理器MyProcessor注册到javac中。首先我们必须提供一个.jar文件。就像其他.jar文件一样,打包我们的注解处理器到此文件中。并且,在我们的jar中需要打包一个特定的文件javax.annotation.processing.ProcessorMETA-INF/services路径下。所以,我们的.jar文件看起来就像下面这样:
MyProcessor.jar
----com
--------example
------------MyProcessor.class
----META-INF
--------services
------------javax.annotation.processing.Processor

com.example.MyProcessor  

MyProcessor.jar放到我们的buildpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器。

上面注册处理器是不是特别的麻烦呢?有没有简单的方式,比如利用一个annotation来搞定?Google就为我们提供了一个这样的annotation:@AutoService(Processor.class)

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
}
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0.1</version>
        </dependency>

在代码的第一行加上@AutoService(Processor.class),它就能自动帮我们生成META-INF/services/javax.annotation.processing.Processor文件

举个栗子:

一、定义一个annotation:

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface FieldConstant {
}

二、定义一个annotation处理器:

@AutoService(Processor.class)
public class FieldConstantProcessor extends AbstractProcessor {

    // 主要是输出信息
    private Messager messager;

    private JavacTrees javacTrees;

    private TreeMaker treeMaker;
    private Names names;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        System.out.println("process");
        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) {
        System.out.println("process");
        // 拿到被注解标注的所有的类
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(FieldConstant.class);
        //如果是类,则对所有的属性进行修改
        for (Element element : elements) {
            System.out.println("element is " + element.getSimpleName().toString());
        }
        return false;
    }

    /**
     * 本处理器想要处理的注解类型的合法全称
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return new HashSet<String>(Arrays.asList(FieldConstant.class.getCanonicalName()));
    }

    /**
     * 用来指定使用的Java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

}

三、定义一个测试类:

@FieldConstant
public class Test {
    /**
     * 创建时间
     */
    private Date createTime;
    

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
public class Main {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test);
    }
}

通过maven编译Main类,看一下编译的输出:

[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\code\sample\target\classes
init
process
element is Test
process
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------


四、问题:

为什么process会打印了两次呢?

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值