lombok 使用原理介绍
使用方法这里就不介绍了,主要使用就是加部分支持的Annotation,然后对应的class会得到对应的加强。
简单来说就是在编译器加强了,常见的aop是在运行期进行的加强(另外说下,aspectj也支持编译时增强)
原理分析
自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。
lombok本质上就是这样的一个实现了”JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:
- javac对源代码进行分析,生成一棵抽象语法树(AST)
- 运行过程中调用实现了”JSR 269 API”的lombok程序
- 此时lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
- javac使用修改后的抽象语法树(AST)生成字节码文件
也就是说在第二部,对于原本的java语法树做了自己的修改
而具体的修改接口,就是实现java rt.jar 里的 javax.annotation.processing.AbstractProcessor
实例代码
参考了几个网上的代码,实践了一下
util 工具类目录如下:
1. AbstractProcessor 逻辑
在MyGetterProcessor里实现里具体的修改语法树的逻辑
@SupportedAnnotationTypes("nyle.annotation.MyGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyGetterProcessor extends AbstractProcessor {
...
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyGetter.class);
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
}
private Name getNewMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
}
2. 就是怎么让javac读取到这个Processor
java在编译的时候会去资源文件夹下读一个META-INF文件夹,这个文件夹下面除了MANIFEST.MF文件之外,还可以添加一个services文件夹,我们可以在这个文件夹下创建一个文件,文件名是javax.annotation.processing.Processor,文件内容是实现的增加processor
然后在编译时 增加-processor,或者使用maven。 maven在编译前会先拷贝资源文件夹,然后当他在编译时候发现了资源文件夹下的META-INF/serivces文件夹时,他就会读取里面的文件,并将文件名所代表的接口用文件内容表示的类来实现。
文件javax.annotation.processing.Processor
内容为
nyle.MyGetterProcessor
3. 打包成jar包
和正常的maven 一样,引用maven-compiler-plugin,但是需要注意的是需要设置不需要proc。
<compilerArgument>-proc:none</compilerArgument>
完整的如下
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<optimize>true</optimize>
<debug>true</debug>
<showDeprecation>true</showDeprecation>
<showWarnings>false</showWarnings>
<compilerArgument>-proc:none</compilerArgument>
<compilerArguments>
<verbose />
<bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar:${JAVA_HOME}/lib/tools.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
测试demo
测试demo就更简单了,引入刚才打包的maven仓库
<dependency>
<groupId>nyle</groupId>
<artifactId>mylombok</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
然后编写一个测试类
可以看到idea还是有点问题,这里应该lombok在idea上的插件做了相应的处理,这里后面可以再研究下。
然后执行build, 可以看到class文件中生成了对应的get方法
执行run,之后,可以看到正常输出结果
参考博客
Lombok原理分析与功能实现 https://blog.mythsman.com/2017/12/19/1/
lombok的使用和原理 https://blog.csdn.net/dslztx/article/details/46715803
官网 https://projectlombok.org/
github https://github.com/rzwitserloot/lombok
lombok自定义扩展实践 https://www.jianshu.com/p/6717d8ea9403