Open CharLemAznable opened 2 years ago
Java的编译过程大致可以分为三个阶段:
而lombok正是利用注解处理
这一步进行实现的.
lombok使用的是JDK6实现的JSR 269: Pluggable Annotation Processing API(编译期的注解处理器)
, 它是在编译期时把lombok的注解代码, 通过AST(抽象语法树)
处理转换为常规的Java代码而实现优雅地编程的.
知道原理远远不够, 所谓"绝知此事要躬行", 利用lombok实现自己的功能才是目的.
参考lombok的文档: Contributing to Project Lombok's development
先获取lombok的源码: lombok git repository on github
本地执行ant eclipse
或者ant intellij
, 再用eclipse或者intellij打开工程
执行ant dist
进行打包, 成功后在dist
路径下就能看到打包后的lombok.jar
PS: 需使用JDK11环境打包
参考文档: Lombok Execution Path
可以看到lombok的加载过程:
首先根据META-INF/services/javax.annotation.processing.Processor
文件的指示, 加载lombok.launch.AnnotationProcessorHider$AnnotationProcessor
处理器, 这个处理器实际上只是一个入口, 主要的功能是启动ShadowClassLoader
用以加载其他的实现类.
ShadowClassLoader
作为lombok的中枢, 并不加载.class
后缀的类文件, 而是加载.SCL.lombok
后缀的类文件, lombok使用这个特殊的后缀是为了向IDE等隐藏其内部的类以及实现, 参考lombok源码中的buildScripts/compile.ant.xml
文件可以看到大部分的.class
文件被做了处理:
<include name="module-info.class" />
<include name="lombok/*.class" />
<include name="lombok/experimental/**" />
<include name="lombok/extern/**" />
<include name="lombok/launch/**" />
<include name="lombok/delombok/ant/Tasks*" />
<include name="lombok/javac/apt/Processor.class" />
<include name="lombok/META-INF/**" />
由ShadowClassLoader
加载的lombok.core.AnnotationProcessor
其实也只是一个代理处理器, 依据lombok的执行环境, 在javac
环境中启动lombok.javac.apt.LombokProcessor
, 或者在ecj(eclipse's compiler)
环境提示警告/错误.
PS: ecj环境中需要使用javaagent方式使用lombok
最后, lombok.javac.apt.LombokProcessor
才是最终完成lombok功能的注解处理器.
lombok使用Java SPI机制加载各注解的对应处理器, 根据优先级顺序进行处理, 例如SneakyThrows需要在NonNull后处理.
现在可以实现嵌码埋点工具了.
新建一个@Retention(RetentionPolicy.SOURCE)
标注的注解
新建一个继承lombok.javac.JavacAnnotationHandler
的注解处理器, 添加@Provides
注解使其可以被加载
抄抄改改HandleSneakyThrows
和HandleCleanup
, 都是一些简单的AST操作
比较特殊的是void类型的方法, 最后一句return;
可省略, 确实有点让人头疼
还是得吐槽一句: 语法糖真不是个好东西!
埋点工具做完了, 接下来就要打包分发了.
打包时需要注意, 因为注解处理器由ShadowClassLoader
加载, 所以如果注解处理器类不在compile.ant.xml
中配置的类文件转换的路径下, 需要修改compile.ant.xml
文件后再执行ant dist
打包.
为了不替代公共的org.projectlombok.lombok
, 仅作为扩展功能包进行分发, 需要将编译好的类文件提取出来.
在lombok工程的dist路径下, unzip解压lombok-${version}.jar, 提取注解的.class
文件和注解处理器的.SCL.lombok
文件.
新建工程, 在resources目录中按类路径放置提取的类文件, 添加META-INF/services/lombok.javac.JavacAnnotationHandler
文件, 填入注解处理器的完整类名保证SPI加载.
此外, 参考ShadowClassLoader
的javadoc:
check any jar files other than our own, loading them via this classloader, if they have a file META-INF/ShadowClassLoader
that contains a line of text with lombok
所以添加META-INF/ShadowClassLoader
文件, 填入lombok
.
剩下的就是朴实无华且枯燥的日常操作了, maven打包, 和手动埋点工具包一起发布到nexus.
业务工程依赖埋点lombok扩展时, 和lombok一样添加<scope>provided</scope>
即可兔死狗烹 鸟尽弓藏.
最近做了个小玩意儿, 要在后端的Java应用里嵌码埋点日志.
手动埋点日志比较简单, 思路就是创建一个独立的logback-LoggerContext, 获取一个干净的logger, 单独配置其Appender处理日志, 只要按slf4j的接口封装出API, 就能基本满足大部分场景的日志需求.
难点在于自动埋点.
第一时间想起的是Instrumentation, 之前也尝试过, [#9 浅尝Java Instrumentation], 只要配合ASM或者Javassist就能实现. 但是有两点不爽的: 一是目标应用启动时要添加agent参数, 而且附带需要分发埋点工具的jar包; 二是已经玩过了, 炒冷饭有点难受.
这时候就想起来一直很好奇的lombok, 它可以SneakyThrows, 可以Cleanup, 还可以Slf4j, 它是如何完成那些神奇的功能的呢?
还是要多学多练.