《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. 总结: