你好呀,我是歪歪。
不是 Log4j 爆出破绽了嘛,而后前几天有小伙伴来问我:我我的项目外面用的是 Lombok 的 @Slf4j 这个会有影响吗?
.png)
你说这事多巧,我也用的这个注解,所以我过后略微的看了一下。
先说论断:有没有影响还是取决于你我的项目中依赖的 log4j2 包,和 Lombok 没有任何关系。
另外“求求你发问之前三思,不要节约咱们的工夫,不要问那些你本人就能搞清楚的问题”这句话不是我说的,是 Lombok 的作者说的:
他为什么会说出这样的略带一丝丝愤慨的话呢?
我带你看看。
从issue 说起
你在 github 上找到 Lombok 我的项目,而后查看它的 issue 会看到对于 log4j 的问题曾经被置顶了:
所以对于这个问题,我就从这个 issue 说起吧,这外面有 Lombok 维护者的权威答复。
https://github.com/projectlom...
这个 issue 的题目翻译过去就是:
论断:对于 log4j 的 0day 问题,Lombok 自身不受影响。
留神啊,这个外面有个十分回味无穷的单词:itself。
个别咱们说 Lombok 不受影响就行了,为什么还得加个 itself(自身)呢?
这外面就是有故事的。
首先是 12 月 10 日下午 5 点 41 分,也就是破绽被爆出的那个下午,有个哥们在 Lombok 的 issue 提出了这个问题:
他说:铁子们,出事了,log4j 爆出惊天大破绽,连忙补吧。
而后有个叫做 Rawi01 的老铁跳出来说:
老哥,你能解释一下这个破绽 对 Lombok 有什么影响吗?据我所知,log4j 只是在运行测试时不呈现编译谬误时才须要。
这个老哥显著是对 Lombok 比拟理解的,他其实曾经指出要害的中央了:Lombok 并没有依赖 log4j。
然而紧接着有人指出:
Lombok 提供了一个注解,叫做 @log4j。这玩意应用了 Log4J 的库吧,毕竟要用它记录日志。
的确有这个注解啊,给大家看一下:
我个别是用 @Slf4j 这个注解,然而它的确是还提供了其余的日志注解,这个先按下不表,等下再说。
当初的主线工作是这个 issue,接着往下看。
接下来,来了一个控场的哥们,看一下他说了啥:
他先艾特了后面的说 @log4j 注解的这个哥们,给他解释说:尽管 Lombok 会生成代码来创立一个 logger 的实例,但咱们并没有 ship, distribute or require 任何特定的版本,如果用户要用这个注解,须要他们本人提供对应的依赖。
这里我有三个词我还没翻译:ship, distribute or require。
require 其实很简略,就是要求。
意思是咱们没有要求用户应用的时候必须给到指定版本的依赖。说白了就是,假如你要用 @Log4j,那就提供对应的依赖,至于这个依赖的版本是什么 Lombok 并不关怀。
剩下的 ship 直译过去是“运送” 的意思,而 distribute 有“散发”的意思。
这个我用一张图片来解释吧:
能够看到,在我的项目中我援用了 Lombok,然而它并没有传递依赖进来任何其余的包。不像是 spring-boot-starter 前面跟着一大坨货色。
我了解这就是他想要表白的:we do not ship, distribute or require any specific version.
而后接着说:在咱们的代码库中的确能够找到一个 log4j2 的版本,但那只是在测试代码外面应用,以便可能无谬误地编译生成的代码。
最初这个哥们总结到:释怀,老铁们,Lombok 依然能够平安的应用。但我感觉尽管是测试代码中援用到了 Log4j ,也还是应该把依赖关系更新到平安的版本。
怎么样,是不是感觉这个老哥谈话有一种不可置疑的自信感。
我读完这个答复之后就是有这样的感觉,隐隐感觉这就是个大佬。
于是我去扒了一下这个老哥的背景:
原本我是想从他的 github 主页上寻找答案,竟然没有发现和 Lombok 相干的我的项目。
而且只有 49 个 followers,看着也不合乎大佬的数据呀:
于是我转战浏览器,搜寻了:Roel Spilker Lombok.
找到了这个:
才发现,好家伙,他是 Lombok 的爸爸啊,难怪谈话这么硬气。
还发现了一个花边新闻:他们之前还把对于 Lombok 的想法奉献给了 Oracle,然而被回绝了。官网不反对,他们只有揭竿而起,本人搞了。
当初 Lombok 的市场占有率还是很高的吧,算是把这事儿干胜利了。
另外,其实这里就能够间接看到他的身份:
Collaborator,就是单干人。他的答复能够等同于官网答复了。
到这里事件还算是倒退的比较顺利,官网亲自下场来解答这个问题,且曾经明说了:
算是能够完结撒花了。
然而,就怕呈现然而啊,前面产生的事件我就感觉有点离谱了。
首先是 SunriseChair 老哥献出了一杀:
他首先援用了作者的回复,而后说,如果你在你的 maven 或者 gradle 中申明 Lombok 这个依赖项,那么 Log4j 的依赖项不也会被包含在内吗?这可能是 classpath 上惟一的依赖,而 Lombok 生成的代码也会用到它?如果我说错了,请纠正我......
他想要表白的是什么意思呢。
首先我感觉是他读完作者的答复后感觉 Lombok 是依赖了 Log4j 的,所以他的外围问题就是,如果我援用了 Lombok 那么 Log4j 的依赖岂不是也会传递进来?
然而作者都说了:there is a version to be found in our code base, but that's 仅仅用于测试类中。
凡是你略微理解一点开源我的项目的工作原理,也就晓得测试相干的局部都不会提供进来,如果你要看测试类,你得把我的项目源码拉下来。
简略来说就是如果你通过 maven 依赖了 Lombok,测试相干的货色你必定是看不到的。
接着一位叫做 RuanNunes 的老哥也进去补枪了,献上了二杀,他说他在整个我的项目外面搜寻到了这个货色:
这个配置是存在破绽的。
而后作者对这两个问题进行了一一回复:
首先,说 Lombok 依赖了 log4j 的老哥听着:Lombok 没有对 log4j 或任何其余库进行依赖。如果你在你的代码中应用 @Log4j 注解,然而又不间接或间接地依赖 log4j ,你的编译就会产生一个错误信息。
对于这一点,后面的依赖剖析截图也曾经阐明了,我就不再贴一次了。
而后说找到破绽的哥们听着:在你找到的这个破绽的同一目录下有一个文本文件,凡是你去看一眼,你就晓得咱们不会将该文件作为咱们公布的一部分。
所以我了解压根不会公布进来,即便有破绽,对于 Lombok 的用户来说也没有任何故障吧?
我也去找了一下作者说的这个文件,就是它:
https://github.com/projectlom...
这个文件外面清清楚楚的说了:not part of lombok itself.
如果后面这两个问题,曾经让作者有点点不爽的感觉的话,那么接下来这个问题,可能就是引爆点,实现三杀,一波带走:
这个哥们上来就说:老铁,我问个问题哈。我应用的是 @slf4j 注解,这个破绽和我有关系吗?
不行了不行了,血压上来了。
我感觉这个哥们发问之前齐全没有看后面的回复,如果他在作者进行了两次解释之前提出这个问题,那么我感觉齐全能够了解。
然而在作者曾经在这个 issue 外面解释了两次“Lombok 不受影响”的前提下,他哗一下,上来就是一个暴击:
老哥,我用的这个注解有问题吗?
我写到这里都有点血压上来了。
这特么特地像是之前有一次在一个群里和他人探讨事务生效的场景,曾经探讨到序幕了,忽然跳进去一个哥们,甩出来一个代码片段,这个片段的写法就是一个经典的 this 调用导致事务注解生效的场景,而这就是咱们刚刚探讨中的一部分。
晓得咱们在探讨这个问题,哪怕你略微往上翻一翻呢,理解一下上下文再发问不好吗?
不扯远了,兴许是我适度解读把,反正我感觉发问的艺术大家都得好好学一下。
回到咱们的主线剧情,看一下作者是怎么答复这个问题的:
他说:老铁,这个问题我帮不了你啊。我对于这个破绽无所不知,我甚至不明确为什么你会认为它也会影响 @Slf4j。
我能够通知你的是,Lombok 没有应用、传递、要求对这些库的依赖。
咱们的工作原理是生成了你"看不见的源代码"。
如果你有任何理由狐疑相似的问题也可能产生在他们的产品中,请分割 Slf4j 的维护者。
不晓得是不是我集体的感觉,我感觉作者回复这个问题的时候曾经有一点喜气了:这都是些什么问题啊?前我曾经回复了两次不受影响啊?怎么感觉我的我的项目使用者怎么都不理解 Lombok 的基本原理呢?
在作者回复完这个冒失的提问者之后,这个提问者还是很礼貌的回复了一下:
谢了老铁,我查看了,我应用的是 SpringBoot 默认的 logback。
作者也把这个问题给置顶了,且批改了这个 issue 的题目。
所以,通过后面的一番解读,当初你再看这个题目:
为什么作者要强调“itself”,因为 Lombok 的确是提供了日志的性能,然而至于援用什么包,哪个版本的包,和 Lombok 都没有任何关系。 Lombok 自身(itself)是平安的。
最最初,作者给了 log4j 破绽对于 Lombok 的 Latest assesment(最新评估),算是总结性发言:
给大家翻译一下要害的货色。
这个破绽只存在于 2.16.0 版本以下的 Log4j code 包中,而不存在于任何其余日志框架中。
Lombok 没有传递依赖任何 Log4j 包,也没有申明对任何货色的依赖。
如果你应用任何 Lombok 的注解,比方 @Log4j,Lombok 将生成应用这些库的代码,然而你的我的项目外面必须要蕴含对这些库的依赖,否则 Lombok 生成的代码将无奈编译。
同样地,你要负责在你的运行时中领有这些包,否则类的初始化可能会失败。
在 Lombok 测试代码中,咱们已经有一个蕴含这个破绽的版本,然而因为测试不解决任何用户输出(测试是硬编码的),而且生成的代码甚至没有被执行,运行测试并没有导致执行测试的机器上呈现 RCE(近程代码/命令执行破绽)。
所以,老铁们,Lombok 自身不须要做任何扭转,也不对你的我的项目负任何平安责任,毕竟包不是咱们引进来的。
如果你不批准目前的评估,请在这个问题上增加评论。
然而,请确保你曾经浏览了其余评论,并确保你了解了这个问题。
最初这两句话,独自拎进去,我可太喜爱这两句话了:
求求你发问之前三思,不要节约咱们的工夫,不要问那些你本人就能搞清楚的问题。
如果你认为咱们脱漏了什么,或者有新的信息,请大声的说进去。
而后,你留神这里作者用的小标题是:The balancing act.
翻译过去是“均衡的行为”,啥玩意?
NO,NO,NO:
一个小俚语送给大家,不用客气。
补充阐明
后面把主线剧情过完了,当初我来几个补充阐明吧。
先说后面按下不表的对于日志的注解。
其实 Lombok 外面对于日志的注解还真是挺多的,能够间接看官网的文档:
https://projectlombok.org/fea...
这么多注解,一个个的讲也没啥意思,我这里就挑 @Slf4j 、@Log4j2 这两个演示一下吧。
首先,咱们能够搞个污浊的 SpringBoot 我的项目,只蕴含这两个依赖:
这个时候如果我什么都不动,只是略微改一下启动类:
而后为了排除烦扰项我把日志打印的级别调整到 Error:
logging.level.root=error
同时敞开 banner 输入:
spring.main.banner-mode=off
banner 就是这个玩意:
这个时候启动我的项目,日志输入是这样的:
能够看到咱们这个时候应用的日志是 logback,起因我在之前的文章外面也讲过了,因为 Springboot 默认应用的日志实现是 logback。
这一点,从我的项目依赖上也能够看进去:
另外,你留神一下,我特意把 import 局部也截进去了,除了 @Slf4j 注解外,这里并没有引入任何日志相干的注解。
而后,我再关注一下这个时候编译进去的 class 文件:
主动引入了 slf4j 相干的包,而后生成了这行代码:
private static final Logger log = LoggerFactory.getLogger(LogdemoApplication.class);
这个时候不晓得你有没有想到编译时注解相干的货色,然而不慌,这里还是先按下不表。
来,我问你:为什么它能引入 slf4j 相干的包?
因为我依赖了呀:
好,如果这个时候我把 logback 的外围依赖给拿掉,会呈现什么事件,你感觉会不会编译不过呢?
不会编译不过,因为 Slf4j 包还在,它只是一个日志门面。
然而运行的时候会抛出异样,因为找不到日志相干的具体实现类:
而后,如果我想用 log4j2 日志实现怎么办呢?
之前的文章中也写过:
把 Springboot 默认的依赖拿掉,而后引入 Log4j2 的包。
这个时候我的项目依赖图是这样的,能够看到没有 logback-core 了,只有 log4j-core:
再次运行我的项目,日志实现就变成了 Log4j:
你发现了吗,我除了动了一下 pom 依赖外,其余的代码一行都没有动,日志框架就从 logback 变动为了 log4j。
而且 class 文件没有任何变动,所以我也就不去截图了。
这就是 Slf4j 的功绩,这就是“门面”的含意,这就是为什么都倡议大家在我的项目中应用 Slf4j,而不是具体的诸如 logback、log4j 这样具体的日志实现。
接下来说一下 @Log4j2 这个注解。
咱们还是把依赖复原到最开始污浊的状态,也就是这样:
而后咱们把注解批改为 @Log4j2,然而咱们我的项目中这个时候并没有引入 Log4j-core 包,那么你感觉会有问题吗?
不会有问题的,咱们能够看一下。
先看一下输入:
此时的日志实现类是 SLF4JLogger。
这玩意哪里来的?
看一下 class 文件:
这个两个类是来自于 log4j-api 包外面,同时因为 log4j-to-slf4j 包的存在,所以最初的实现类桥接到了 SLF4JLogger 中去:
如果我把 log4j-api 包移除掉,你说会不会编译不过呢?
必定编译不过的,因为包都不存在了,搞不进去 class 文件:
如果我不想用 SLF4JLogger 这个类呢,我就想用真正的 log4j。
简略,把 log4j 的依赖搞进来:
好,我后面说了这么多的废话,不厌其烦的给你排除、引入日志相干的包,给你看输入啥的,而且整个过程中并不波及到 Lombok 包的变动,都是为了再次印证这两句话:
如果你应用任何 Lombok 的注解,比方 @Log4j,Lombok 将生成应用这些库的代码,然而你的我的项目外面必须要蕴含对这些库的依赖,否则 Lombok 生成的代码将无奈编译。
比方我后面把 log4j-api 包移除掉了,是不是编译就没有过?
同样地,你要负责在你的运行时中领有这些包,否则类的初始化可能会失败。
比方我后面把 logback-core 的包移除了,编译的时候没有问题,然而服务运行的时候,是不是抛出找不到类的异样?
是不是再次证实:
聊聊原理
后面我提到了一句“编译时注解”,不晓得大家对于这个玩意了不理解。
不理解其实也很失常,因为咱们写业务代码的时候很少自定义编译时注解,顶天了搞个运行时注解就差不多了。
Lombok 的外围工作原理就是编译时注解。
其实我理解的也不算深刻,只是大略晓得它的工作原理是什么样的,对于源码没有深入研究。
然而我能够给你分享一下两个须要留神的中央和能够去哪里理解这个玩意。
首先第一个须要留神的中央是这里:
log 相干注解的源码位于这个局部,能够看到很奇怪啊,这些文件是以 SCL.lombok 结尾的,这是什么玩意?
这是 lombok 的小心理,其实这些都是 class 文件,然而为了防止净化用户我的项目,它做了非凡解决。
所以你关上这类文件的时候抉择以 class 文件的模式关上就行了,就能够看到外面的具体内容。
比方你能够看看这个文件:
lombok.core.handlers.LoggingFramework
你会发现你们就像是枚举似的,写了很多日志的实现:
这个外面把每个注解须要生成的 log 都硬编码好了。正是因为这样,Lombok 才晓得你用什么日志注解,应该给你生成什么样的 log。
比方 log4j 是这样的:
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class);
这也同时能够和咱们后面的 class 文件对应起来:
而 SLF4J 是这样的:
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class);
第二个须要留神的中央是找到入口:
这些 class 文件加载的入口在于这个中央,是基于 Java 的 SPI 机制:
AnnotationProcessorHider 这个类外面有两行动态外部类,咱们看其中一个, AnnotationProcessor ,它是继承自 AbstractProcessor 抽象类:
javax.annotation.processing.AbstractProcessor
这个抽象类,就是入口中的入口,外围中的外围。
在这个入口外面,初始化了一个类加载器,叫做 ShadowClassLoader:
它干的事儿就是加载那些被标记为 SCL.lombok 的 class 文件。
而后我是怎么晓得 Lombok 是基于编译时注解的呢?
其实这玩意在我看过的两本书外面都有写,有点含糊的印象,写文章的时候我又翻出来读了一遍。
首先是《深刻了解 Java 虚拟机(第三版)》的第四局部程序编译与代码优化的第 10 章:前端编译与优化一节。
外面专门有一大节,说插入式注解的:
Lombok 的次要工作地盘,就在 javac 编译的过程中。
在书中的 361 页,提到了编译过程的几个阶段。
从Java代码的总体构造来看,编译过程大抵能够分为一个筹备过程和三个处理过程:
- 1.筹备过程:初始化插入式注解处理器。
2.解析与填充符号表过程,包含:
- 词法、语法分析。将源代码的字符流转变为标记汇合,结构出形象语法树。
- 填充符号表。产生符号地址和符号信息。
- 3.插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段,本章的实战局部会设计一个插入式注解处理器来影响Javac的编译行为。
4.剖析与字节码生成过程,包含:
- 标注查看。对语法的动态信息进行查看。
- 数据流及控制流剖析。对程序动静运行过程进行查看。
- 解语法糖。将简化代码编写的语法糖还原为原有的模式。(java中的语法糖包含泛型、变长参数、主动装拆箱、遍历循环foreach等,JVM运行时并不反对这些语法,所以在编译阶段须要还原。)
- 字节码生成。将后面各个步骤所生成的信息转换成字节码。
如果说 javac 编译的过程就是 Lombok 的工作地盘,那么其中的“插入式注解处理器的注解处理过程”就是它的工位了。
书中也提到了 Lombok 的工作原理:
.png)
第二本书是《深刻了解 JVM 字节码》,在它的第 8 章,也具体的形容了插件化注解的解决原理,其中也提到了 Lombok:
.png)
最初画了一个示意图,是这样的:
.png)
如果你看懂了书中的后面的十几页的形容,那么看这个图就会比拟清晰了。
总之,Lombok 的外围原理就是在编译期对于 class 文件的魔改,帮你生成了很多代码。
这也是作者提到的:
invisible source code,看不见的源码。
这里的看不见,指的是 java 文件中的看不见,在 class 文件中它还是无处遁形。
如果你有趣味深刻理解它的原理的话,能够去看看我后面提到的这两本书,外面都有手把手的实际开发。
我就不写了,一个起因是因为的确门槛较高,写进去生涩难懂。另外一个起因那不是因为我懒嘛。
本文曾经收录至集体博客,欢送大家来玩:
https://www.whywhy.vip/