Java实践指南:编译时自定义注解处理器

2023-06-19 14:34:05 浏览数 (1)


前沿


java中的注解(Annotation),我们每天每时每刻都在看到它,尤其是spring项目中。

运行时的注解 反射的合理利用,让一些功能的实现变的很容易,比如spring中的注解org.springframework.beans.factory.annotation.Autowired、org.springframework.beans.factory.annotation.Value等实现了注入值的一些功能。

其实java中的注解也可以在编译期间被我们所利用,比如自动代码生成框架lombok,google开源https://github.com/google/auto等。

编译时java注解如何使用


比如我们自定义了一个注解,这个注解在项目中只能被使用一次,如果被使用到多次的时候就会让编译失败。

  • 注解定义
代码语言:javascript复制
package com.renzhikeji.annotation.processor;

import java.lang.annotation.*;

/**
 * @author 认知科技技术团队
 * 微信公众号:认知科技技术团队
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RetryDatasource {
    String value() default "";
}

注意:我们的注解必须能在编译阶段能够保留,这由RetentionPolicy来定义的。

  • 编写自定义注解处理器Processor
代码语言:javascript复制

/**
 * @author 认知科技技术团队
 * <p>
 * 微信公众号:认知科技技术团队
 */
public class RetryDataSourceCheckProcessor extends AbstractProcessor {

    public RetryDataSourceCheckProcessor() {
        super();
        System.out.println("RetryDataSourceCheckProcessor()============================");
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        System.out.println("getSupportedAnnotationTypes()============================"   RetryDatasource.class.getCanonicalName());
        return Set.of(RetryDatasource.class.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        System.out.println("getSupportedSourceVersion()============================"   SourceVersion.latestSupported());
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        System.out.println("init()============================");

    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("==============process =============="   annotations);
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotation);
            System.out.println(""   annotation   elementsAnnotatedWith);
            if (elementsAnnotatedWith.size() > 1) {
                throw new IllegalArgumentException("注解"   annotation   "用在了多个类型上"   elementsAnnotatedWith);
            }
        }
        return true;
    }
}

为了了解每个方法的运行,代码随便打印了些日志。

注意:

  • 自定义注解处理器必须包含一个public修饰的无参构造函数,ServiceLoader需要反射实例化。
  • 需要覆盖方法com.renzhikeji.annotation.processor.RetryDataSourceCheckProcessor#getSupportedAnnotationTypes,指定我们的注解处理器处理的注解类型。
  • 尽量覆盖方法com.renzhikeji.annotation.processor.RetryDataSourceCheckProcessor#getSupportedSourceVersion,指定我们的java源码版本,而不使用其默认实现。
  • 根据SPI,在目录META-INF/services下新建文件javax.annotation.processing.Processor

文件内容:

代码语言:javascript复制
com.renzhikeji.annotation.processor.RetryDataSourceCheckProcessor

注意:此文件需要和上述两个步骤中的代码位于不同的jar包中,避免编译SPI找不到类失败。

运行示例

maven编译时会报错:

代码语言:javascript复制
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile 
(default-compile) on project demo-api: Fatal error compiling: 
java.lang.IllegalArgumentException: 
注解corenzhikeji.annotation.processor.RetryDatasource用在了多个类型上
[com.renzhikeji.demo.api.demoapi.DemoApiApplication, 
com.renzhikeji.demo.api.demoapi.demo.Test] -> [Help 1]
[ERROR] 

源码地址:

https://gitee.com/whoamiy/demo/tree/java_Annotation_Processor/

总结


java的注解不仅仅在运行时利用反射提供很多的功能,而且在编译时也能为我们实现很多的功能,比如代码规则校验、代码自动生成等。


0 人点赞