我们接触的注解主要分为以下两类
- 运行时注解:通过反射在运行时动态处理注解的逻辑
- 编译时注解:通过注解处理器在编译期动态处理相关逻辑
编译期注解我们常用的有Lombok,在class文件中自动生成get和set方法
解编译期处理流程最关键的一个类就是javax.annotation.processing.Processor注解处理器的接口类,遵循SPI规约进行拓展,我们所有需要对编译期处理注解的逻辑都需要实现这个Processor接口,当然,AbstractProcessor 抽象类帮我们写好了大部分都流程,所以我们只需要实现这个抽象类就可以很方便的定义一个注解处理器;
自定义注解处理器
(1)tools.jar加入到idea的类路径
注意:在idea自定义编译处理器前,需要把tools.jar加入到idea的类路径中(文件--》项目结构--》平台设置--》SDK-->类路径--》把tools.jar加入进去),如下图:
(2)建maven父子项目
1)父项目demo的pom.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wbq</groupId>
<artifactId>demo</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>demo</name>
<description>demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<modules>
<module>child1</module>
<module>child2</module>
<module>child3</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions><!-- 去掉默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.7.3</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2)子项目child3的pom.xml如下
- pom.xml
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.wbq</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>child3</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<!-- 指定JDK编译版本 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-proc:none</arg>
</compilerArgs>
<compilerArguments>
<bootclasspath>
${java.home}/lib/rt.jar;${java.home}/lib/jce.jar;${java.home}/../lib/tools.jar
</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 注解类
package com.wbq.child3.processor;
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.RUNTIME)
public @interface MyFieldAnnotation {
}
-
AbstractProcessor实现类
在有MyFieldAnnotation注解的类的方法中添加代码:System.out.println("hello world");
package com.wbq.child3.processor;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
/**
* 在有MyFieldAnnotation注解的类的方法中添加代码:System.out.println("hello world");
*/
@SupportedAnnotationTypes("com.wbq.child3.processor.MyFieldAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//@AutoService(Processor.class)
public class MyFieldProcessor extends AbstractProcessor {
/**
* 抽象语法树
*/
private JavacTrees javacTrees;
/**
* AST
*/
private TreeMaker treeMaker;
/**
* 标识符
*/
private Names names;
/**
* 日志处理
*/
private Messager messager;
private Filer filer;
public synchronized void init(ProcessingEnvironment processingEnv) {
System.out.println("初始化");
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "初始化 ");
javacTrees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment)processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
super.init(processingEnv);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("注解处理器");
annotations.stream().flatMap(t -> roundEnv.getElementsAnnotatedWith(t).stream())
.forEach(t -> {
JCTree tree = javacTrees.getTree(t);
tree.accept(new TreeTranslator() {
@Override
public void visitMethodDef(JCTree.JCMethodDecl tree) {
System.out.println("hello world");
JCTree.JCStatement sysout = treeMaker.Exec(
treeMaker.Apply(
List.nil(),
select("System.out.println"),
List.of(treeMaker.Literal("hello world"))
)
);
//覆盖原有的语句块
tree.body.stats = tree.body.stats.append(sysout);
super.visitMethodDef(tree);
}
});
});
return true;
}
private JCTree.JCFieldAccess select(JCTree.JCExpression selected,String expressive){
return treeMaker.Select(selected, names.fromString(expressive));
}
private JCTree.JCFieldAccess select(String expressive){
String[] exps=expressive.split("\\.");
JCTree.JCFieldAccess access=treeMaker.Select(ident(exps[0]),names.fromString(exps[1]));
int index=2;
while(index<exps.length){
access=treeMaker.Select(access, names.fromString(exps[index++]));
}
return access;
}
private JCTree.JCIdent ident(String name){
return treeMaker.Ident(names.fromString(name));
}
}
- 指定插入式注解类
在resources下新建META-INF文件夹,META-INF文件夹下新建services文件夹,services文件夹下新建文件(文件名=javax.annotation.processing.Processor)
src/main/resources/META-INF/services/javax.annotation.processing.Processor
javax.annotation.processing.Processor文件中写入
com.wbq.child3.processor.MyFieldProcessor
3)新建子项目child2
child2使用child3中的注解
结构如下:
- pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>com.wbq</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>child2</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.wbq</groupId>
<artifactId>child3</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 测试类如下:
package com.wbq.child2;
import com.wbq.child3.processor.MyFieldAnnotation;
//child3中的注解
@MyFieldAnnotation
public class TestChild3Main {
public static void main(String[] args) {
System.out.println("main");
}
}
(3)测试运行
- 在maven中先编译child3,依次:clean、compile、package、install
在maven中编译child2,依次:clean、compile
在child2\target\classes\com\wbq\child2中生成了TestChild3Main.class文件,使用idea打开这个class文件,Main方法中添加了一行代码System.out.println("hello world");