⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.im/post/5d2b09efe51d455d877e0dc4 「我想问问天」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

注解处理器初探

平时做项目中有个非常好用的一个插件,叫lombok.它提供了一些简单的注解,可以用来生成javabean和一些getter/setter方法,提高了开发的效率节省了开发时间. 今天我们就来看看lombok使用的什么方式来实现这种操作的.其实lombok使用的是annotation processor,这个是jdk1.5中增加的新功能.像@Getter只是一个注解,它真正的处理部分 是在注解处理器里面实现的.官方参考链接.

背景介绍

注解处理器其实全称叫Pluggable Annotation Processing API,插入式注解处理器,它是对JSR269提案的实现,具体可以看链接里面的内容,JSR269链接. 它是怎么工作的呢?可以参考下图:

1.parse and enter:解析和输入,java编译器这个阶段会把源代码解析生成AST(抽象语法分析树) 2.annotation processing:注解处理器阶段,此时将调用注解处理器,这时候可以校验代码,生成新文件等等(处理完可以循环到第一步) 3.analyse and generate:分析和生成,此时前两步完成后,生成字节码(这个阶段进行了解糖,比如类型擦除) 这些其实只是为了给大家留有一个粗浅的印象,它是怎么执行的.

实践

看了上面的资料,大脑中应该有了一个大概的印象,现在我们实际操作一下写一个简单的例子,实践一下. 要使用注解处理器需要两个步骤: 1.自定义一个注解 2.继承AbstractProcessor并且实现process方法

我们接下来写一个很简单的例子,就是在一个类上加上@InterfaceAnnotation,编译的时候去生成一个"I"+类名的接口类. 首先我这里是定义了两个moudle,一个用来写注解和处理器,另一个用来调用注解.

第一步:自定义一个注解

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

1.@Target:表示的是这个注解在什么上面使用,这里ElementType.TYPE是指在类上使用该注解 2.@Retention:表示的是保留到什么阶段,这里RetentionPolicy.SOURCE是源代码阶段,编译后的class上就没有这个注解了

第二步:继承AbstractProcessor并且实现process方法

@SupportedAnnotationTypes(value = {"com.example.processor.InterfaceAnnotation"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class InterfaceProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "进入到InterfaceProcessor中了~~~");
// 将带有InterfaceProcessor的类给找出来
Set<? extends Element> clazz = roundEnv.getElementsAnnotatedWith(InterfaceAnnotation.class);
clazz.forEach(item -> {
// 生成一个 I + 类名的接口类
String className = item.getSimpleName().toString();
className = "I" + className.substring(0, 1) + className.substring(1);
TypeSpec typeSpec = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC).build();

try {
// 生成java文件
JavaFile.builder("com.example.processor", typeSpec).build().writeTo(new File("./src/main/java/"));
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
}

1.@SupportedAnnotationTypes:表示这个processor类要对什么注解生效 2.@SupportedSourceVersion:表示支持的java版本 3.annotations:被要求的注解,就是@SupportedAnnotationTypes对应的注解 4.roundEnv:存放着当前和上一轮processing的环境信息 5.TypeSpec这个可能有点没看懂是干嘛的,它是javaPoet中的一个类,javaPoet是java用于生成java文件的一款第三方插件很好用,所以这里使用了这个类来生成java文件, 实际上这里用java自带的PrintWriter等输入输出流也可以生成java文件,生成文件有很多方式.javaPoet的链接.javaPoet使用指南. 6.Messager是用来打印输出信息的,System.out.println其实也可以; 7.process如果返回是true后续的注解处理器就不会再处理这个注解,如果是false,在下一轮processing中,其他注解处理器也会来处理改注解.

写好之后,这里需要指定processor,META-INF/services/javax.annotation.processing.Processor 写好com.example.processor.InterfaceProcessor.如果你不知道这是啥,可以看下我另一篇博客(实力推广XD)什么是SPI 我们在把注解处理器给编译好,maven里插件的设置:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 不加这一句编译会报找不到processor的异常-->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>

此时的目录结构是这样:

.
├── HELP.md
├── pom.xml
├── processor.iml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── processor
│ ├── InterfaceAnnotation.java
│ └── InterfaceProcessor.java
└── resources
└── META-INF
└── services
└── javax.annotation.processing.Processor

然后mvn clean install.

第三步:使用注解

在使用之前呢,注解处理器要是编译好的.引入注解处理器的jar包. 测试类加上@InterfaceAnnotation

@InterfaceAnnotation
public class TestProcessor {
}

maven指定编译时使用的注解处理器.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<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>
com.example.processor.InterfaceProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>

此时目录结构是

.
├── HELP.md
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── test
│ │ └── TestProcessor.java
│ └── resources
└── test.iml

然后mvn compile,生成了java文件,此时目录结构是:

.
├── HELP.md
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── processor
│ │ │ └── ITestProcessor.java // 这里就是生成的java文件
│ │ └── test
│ │ └── TestProcessor.java
│ └── resources
├── target
│ ├── classes
│ │ └── com
│ │ └── example
│ │ └── test
│ │ └── TestProcessor.class
│ ├── generated-sources
│ │ └── annotations
│ └── maven-status
│ └── maven-compiler-plugin
│ └── compile
│ └── default-compile
│ ├── createdFiles.lst
│ └── inputFiles.lst
└── test.iml

看到了生成的java文件就大功告成~

总结:

1.java注解处理器在很多地方都可以使用,实际应用比如lombok,安卓生成fragment等等,只使用一个注解可以省去很多代码,提高效率; 2.本文只是列举了一个很简单的例子,很多注解处理器里面的api都没有使用到,读者有兴趣的可以自行研究,而且有涉及到抽象语法树的api; 3.注解处理器可以用于生成新的类来完成某些功能,但是不能直接修改当前的类.

文章目录
  1. 1. 背景介绍
  2. 2. 实践
    1. 2.1. 第一步:自定义一个注解
    2. 2.2. 第二步:继承AbstractProcessor并且实现process方法
    3. 2.3. 第三步:使用注解
  3. 3. 总结: