一、注解
注解相关内容,可自行百度。lombok中注解,比如setter、getter等,实现是怎么样呢?本质是通过语法树,修改源码的方式。这里自己实现了一个简单注解:主要实现在方法入口、退出打印。
二、idea创建工程
通过idea创建一个工程,并且添加两个module,如下图所示:
2.1、ztrace工程
2.1.1、pom文件
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study.trace</groupId>
<artifactId>ztrace</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 用于调试processor代码 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc7</version>
</dependency>
<!-- 编译有用 -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<!-- 编译顺序 先编译processor-->
<execution>
<id>default-compile</id>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<includes>
<include>com/study/ztrace/ZTraceProcessor.java</include>
</includes>
</configuration>
</execution>
<!-- 编译其他java文件 -->
<execution>
<id>compile-project</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.1.2、源码代码
//ZTrace.java
package com.study.ztrace;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ZTrace {
}
//ZTraceProcessor.java
package com.study.ztrace;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.model.JavacElements;
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.util.Context;
import com.sun.tools.javac.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes({"com.study.ztrace.ZTrace"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class ZTraceProcessor extends AbstractProcessor {
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
final JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils();
final TreeMaker treeMaker = TreeMaker.instance(context);
Set<? extends Element> elements = roundEnv.getRootElements();
for (Element element : roundEnv.getElementsAnnotatedWith(ZTrace.class)) {
JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
treeMaker.pos = jcMethodDecl.pos;
jcMethodDecl.body = treeMaker.Block(0, List.of(
treeMaker.Exec(
treeMaker.Apply(
List.<JCTree.JCExpression>nil(),
treeMaker.Select(
treeMaker.Ident(
elementUtils.getName("logger")
),
elementUtils.getName("info")
),
List.<JCTree.JCExpression>of(
treeMaker.Literal(jcMethodDecl.getName().toString() + " {") //method name
)
)
),
jcMethodDecl.body,
treeMaker.Exec(
treeMaker.Apply(
List.<JCTree.JCExpression>nil(),
treeMaker.Select(
treeMaker.Ident(
elementUtils.getName("logger")
),
elementUtils.getName("info")
),
List.<JCTree.JCExpression>of(
treeMaker.Literal("}")
)
)
)
));
}
return false;
}
}
2.1.3、resources文件
在resources目下创建META-INF/services/javax.annotation.processing.Processor文件,文件内容如下:
com.study.ztrace.ZTraceProcessor
2.1.4、编译
我们可以在通过命令行进行,mvn clean install 即可
2.2、tracedemo工程
2.2.1、pom文件
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study.trace</groupId>
<artifactId>tracedemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>com.study.trace</groupId>
<artifactId>ztrace</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2.2、源码代码
//Main.java
package com.tracedemo;
import com.study.ztrace.ZTrace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
@ZTrace
public void add(int a, int b) {
int r = a + b;
System.out.println("a+b=" + r); //这里只有一个打印啊
}
public static void main(String[] args) throws Exception{
new Main().add(512, 512);
}
}
2.3.3、resources文件
在resources目录下面创建log4j.properties配置文件,用于配置log
### set log levels ###
log4j.rootLogger = debug,stdout,D,E
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %p [%c] %m%n
### 输出到日志文件 ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 保存异常信息到单独文件 ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### logger ###
# 设置后com.xiaofan路径下的日志将只输出ERROR日志
#log4j.logger.com.xiaofan=ERROR
2.2.4、编译
通过idea直接运行即可,正常可以输出如下内容:
2020-11-26 16:47:21 INFO [com.tracedemo.Main] add {
a+b=1024
2020-11-26 16:47:21 INFO [com.tracedemo.Main] }
其中:log info内容是通过ZTrace注解实现的,目前写的ZTrace只能在void类型方法使用。
三、调试AbstractProcessor
继承AbstractProcessor的类,不能直接调试(在编译期,非运行期),网上有很多方式,但是我只成功autoservice这种方式。这里把调试步骤详细说明一下(以上代码已经写好):
3.1、引用autosevice的jar包
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc7</version>
</dependency>
3.2、在AbstactProcessor的实现类中添加AutoService注解
@SupportedAnnotationTypes({"com.study.ztrace.ZTrace"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class ZTraceProcessor extends AbstractProcessor {
以下环境配置,都是在tracedemo工程中设置(即使用方进行)
3.3、设置idea远程调试模式,如下图:
3.4、开启Annotation Processor
3.4、运行
在命令行中执行mvnDebug
然后在idea设置断点并执行debug模式,效果如下:
四、总结
之前对lombok注解功能感觉太神秘了,所以自己花了一些时间研究了一下,揭开这个面纱,当了解后发现我们还可以去做更多东西。练习过程中参考了,如下内容博客
https://www.cnblogs.com/throwable/p/9139908.html
https://nicky-chen.github.io/2019/05/03/apt_lombok_implement/
https://blog.youkuaiyun.com/dap769815768/article/details/90448451
https://blog.youkuaiyun.com/vector_M/article/details/105037394