从Lombok到JSR269

从原始码了解Lombok运作原理,进而认识JSR269更多的实作方案,之后,在开发上,我们就可灵活运用编译与执行时期的标注处理

如果对于Java开发时,经常须面对Getters、Setters、equals等样版(Boilerplate)程式码感到厌烦,Lombok是个便捷的方案,只要类别路径包含JAR,在原始码加入几个标注,就可以自动生成对应的方法,无需额外的容器,就能够立即使用。对于这么方便的东西,身为好奇的开发者,当然要打开原始码,来研究一下背后的原理。

编译时期标注处理

谈到Java最令人讨厌的一件事,就是面对POJO(Plain Old Java Object)的那些Getter、Setter了。是的,整合开发工具(IDE)都提供自动产生Getter、Setter程式码的功能,不过问题不只在于自动产生,阅读原始码时,这类样版程式码会形成一种干扰,在修改或增删值域之后,就算有重构工具的辅助,清理这类原始码也是个麻烦。

使用Lombok,我们可在值域上标注@Getter、@Setter,就能自动产生对应的方法;想指定某些值域来产生对应的toString方法,可用@ToString;要产生equals与hashCode,能用@ EqualsAndHashCode;若是建构式,就用@AllArgsConstructor等标注。如果连这些标注都懒得加,只需在类别上标注@Data,就能搞定一切;若想要一个值物件,就标注@Value(就像不产生Setter的@Data),这通常被做为一个Immutable物件来使用。

同时,Lombok与整合开发工具的程式码自动产生功能,并不相同,因为既有的原始码不会增加任何内容。然而,引用被Lombok标注的类别时,在编译时期就能呼叫自动产生的方法。也就是说,这一切都是在编译时期处理好了,Lombok标注的类别在编译时期,就产生了对应的方法,储存在.class之中,而Lombok本身也附带delombok,能将加过料的. class反组译回.java档案。

那么,Lombok提供了自己的编译器吗?不是的,在javac编译时,只要包含了Lombok的JAR档案,就可以了(想整合整合开发工具,如Eclipse,可以透过JAR来安装plugin),这是因为JAR档案中,META-INF/services里的javax.annotation.processing.Processor指定了Lombok的标注处理器(Annotation Processor)类别,而javac在完成原始码的剖析之后,若发现标注处理器的部份,就会介入处理。

认识JSR269

自Java 5导入了标注(Annotation)之后,整个Java生态圈大量运用它,来简化Java程式码的撰写、隐藏细节、突显关切点,而这早就不是什么新鲜事了。常见的标注应用是在执行时期,运用反射API取得标注资讯,载入类别生成实例,甚至是动态生成代理物件来做对应的处理。

就标注本身而言,其实预设是只将标注资讯储存于.class档案,可被编译器或位元码分析工具读取;甚至Java本身提供的@SuppressWarnings、@Override标注,还被设定为RetentionPolicy .SOURCE,而且,标注资讯只用在编译时期,.class不会留下标注资讯,执行时期也就无法读取标注资讯;若要能于执行时期读取,反而须特别搭配RetentionPolicy.RUNTIME,就这点来看,当初Java导入标注功能时,似乎比较倾向将标注用于编译器等静态工具。

那么,如何在编译时期读取标注资讯?像AspectJ那样改写个编译器?

在Java 5导入标注时,其实就提供了个标注处理工具(Annotation Processing Tool,APT),我们可以使用非标准的com.sun.mirror等API,来撰写注解处理器,再透过apt工具程式,于静态时期处理标注。

接着,在Java 6中,纳入了JSR269:Pluggable Annotation Processing API,将标注处理器的API(套件为javax.annotation.processing、javax.lang.model等)标准化了,而Java 7将apt工具与原先的非标准API,标示为废弃(deprecated),等到后续在Java 8中,正式移除apt与com.sun.mirror等相关API。

具体来说,现在开发者可继承AbstractProcessor,使用@SupportedSourceVersion指定Java版本,@SupportedAnnotationTypes指定要处理的标注类型名称,并定义process方法来处理标注,而在javac编译时,若使用-processor或--processor -path指定标注处理器来源,或者在类别路径包含的JAR中,META-INF里面,存在如同上述的javax.annotation.processing.Processor设定,在编译器剖析、生成语法树之后,若原始码出现了指定要处理的标注,就会载入标注处理器并执行process方法。

JSR269的应用

Lombok的标注处理器就运用了JSR269,主要是定义在lombok.javac.apt.LombokProcessor(https://goo.gl/h8QUs9),其中,运用了非标准的Java Compiler Tree API(om.sun.tools .javac等API)修改语法树,之后,将修改过的结果交由编译器分析、产生位元组码,并储存为.class。

运用Java Compiler Tree API本身来说,是比较复杂的,而且,它不是公开的标准API,直接修改语法树来改变位元组码的输出,也像是在改变Java的语法,Lombok本身甚至有lombok .val、lombok.var型态,可以撰写如下的程式码:

 

var x = new User ();

 

做为第三方工具程式库,却改变语言规则,不少开发者认为这是不明智的决定。事实上,这就与Java 10的var语法发生冲突,直接修改语法树来改变位元组码的输出。在〈Don't use Lombok〉(https://goo.gl/DYy9jt),也谈到一些隐忧,例如,误解或误用Lombok的行为,或因四处呼叫产生的方法,在决定不使用Lombok时,反而造成许多问题,delombok出来的原始码也变得丑陋,难以阅读。

然而,我们还是可以使用JSR269,来实作编译时期检查工具,或者是程式码产生器。

例如,在处理某个标注时,在某些条件下抛出例外,以中断编译过程,像是实作个@MyOverride来模仿标准的@Override行为;或者是在发现某些标注时,结合JavaPoet、 JavaWriter之类的Java原始码产生程式库,自动生成相关的工具类别原始码,若必要,也可以使用标准的Java Compiler API,来进一步对产生的原始码进行编译。

Google的AutoValue程式库(https://goo.gl/j6T66h)就是基于JSR269,可自动为被标注类别,产生具有Getter、Setter等方法的原始码;而对于值物件,也可以使用Immutables程式库(https://goo.gl/WgRph3),它也是基于JSR269来产生Immutable物件建构器的原始码。

改变位元组是好是坏,或许也是要看做法,例如,AspectJ也是改变位元组码,不过是用来织入横切主要流程的关切点,而被织入的流程是个可抽换的服务,不影响应用程式的主要功能,也不增加额外的方法,相对来说,就不会有预期之外的行为。

研究JSR269的实作

基本上,在某些专案上,适当地运用Lombok,还是有帮助的。至少对付POJO那些样版方法时,使用@Data之类的标注,相对来说省事许多。全面采用Lombok,或许会有些隐忧,然而从对Java语言进行Hack的角度来看,Lombok是一套满有趣、具有想像力的程式库。

除了使用Lombok、AutoValue、Immutables之外,有机会的话,可以看看它们的原始码是如何实作,对于静态时期如何善用标准处理来辅助开发,会有不少的心得,在搭配执行时期的标注处理上,手边可用的工具就会更加丰富了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值