在面向?qū)ο缶幊讨斜夭豢缮傩枰诖a中定義對(duì)象模型蜘澜,而在基于Java的業(yè)務(wù)平臺(tái)開發(fā)實(shí)踐中尤其如此复旬。相信大家在平時(shí)開發(fā)中也深有感觸,本來是沒有多少代碼開發(fā)量的步势,但是因?yàn)槎x的業(yè)務(wù)模型對(duì)象比較多氧猬,而需要重復(fù)寫Getter/Setter、構(gòu)造器方法坏瘩、字符串輸出的ToString方法和Equals/HashCode方法等盅抚。那么是否一款插件或工具能夠替大家完成這些繁瑣的操作呢?本文將向大家介紹一款在Eclipse/Intellij IDEA主流的開發(fā)環(huán)境中都可以使用的Java開發(fā)神器倔矾,同時(shí)簡要地介紹下其背后自定義注解的原理泉哈。
Lombok的簡介
Lombok是一款Java開發(fā)插件,使得Java開發(fā)者可以通過其定義的一些注解來消除業(yè)務(wù)工程中冗長和繁瑣的代碼破讨,尤其對(duì)于簡單的Java模型對(duì)象(POJO)。在開發(fā)環(huán)境中使用Lombok插件后奕纫,Java開發(fā)人員可以節(jié)省出重復(fù)構(gòu)建提陶,諸如hashCode和equals這樣的方法以及各種業(yè)務(wù)對(duì)象模型的accessor和ToString等方法的大量時(shí)間。對(duì)于這些方法匹层,它能夠在編譯源代碼期間自動(dòng)幫我們生成這些方法隙笆,并沒有如反射那樣降低程序的性能。
在Intellij中安裝Lombok的插件
想要體驗(yàn)一把Lombok的話升筏,得先在自己的開發(fā)環(huán)境中安裝上對(duì)應(yīng)的插件撑柔。通過IntelliJ的插件中心尋找Lombok并安裝。
另外需要注意的是您访,在使用lombok注解的時(shí)候記得要導(dǎo)入lombok.jar包到工程铅忿,如果使用的是Maven的工程項(xiàng)目的話,要在其pom.xml中添加依賴如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
好了灵汪,就這么幾步后就可以在Java工程中開始用Lombok這款開發(fā)神器了檀训。下文將會(huì)給大家介紹Lombok中一些注解的使用方法,讓大家對(duì)如何用這些注解有一個(gè)大致的了解享言。
Lombok注解使用方法
Lombok常用注解介紹
下面先來看下Lombok中主要幾個(gè)常用注解介紹:
Val 可以將變量申明是final類型
@NonNull 能夠?yàn)榉椒ɑ驑?gòu)造函數(shù)的參數(shù)提供非空檢查
@Cleanup 能夠自動(dòng)釋放資源
@Getter/@Setter 可以針對(duì)類的屬性字段自動(dòng)生成Get/Set方法
@ToString 使用該注解的類生成一個(gè)toString方法
@EqualsAndHashCode 使用該注解的類自動(dòng)生成equals和hashCode方法
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,這幾個(gè)注解分別為類自動(dòng)生成了無參構(gòu)造器峻凫、指定參數(shù)的構(gòu)造器和包含所有參數(shù)的構(gòu)造器。
@Data注解作用比較全览露,其包含注解的集合@ToString荧琼,@EqualsAndHashCode,所有字段的@Getter和所有非final字段的@Setter, @RequiredArgsConstructor等
@Builder注解提供了一種比較推崇的構(gòu)建值對(duì)象的方式
@Synchronized注解類似Java中的Synchronized 關(guān)鍵字差牛,但是可以隱藏同步鎖
Lombok的基本使用示例
(1)val 可以將變量申明是final類型命锄。
public static void main(String[] args) {
val setVar = new HashSet<String>();
val listsVar = new ArrayList<String>();
val mapVar = new HashMap<String, String>();
//=>上面代碼相當(dāng)于如下:
final Set<String> setVar2 = new HashSet<>();
final List<String> listsVar2 = new ArrayList<>();
final Map<String, String> maps2 = new HashMap<>();
}
(2)@NonNull注解能夠?yàn)榉椒ɑ驑?gòu)造函數(shù)的參數(shù)提供非空檢查。
public void notNullExample(@NonNull String string) {
//方法內(nèi)的代碼
}
//=>上面代碼相當(dāng)于如下:
public void notNullExample(String string) {
if (string != null) {
//方法內(nèi)的代碼相當(dāng)于如下:
} else {
throw new NullPointerException("null");
}
}
(3)@Cleanup注解能夠自動(dòng)釋放資源多糠。
public void jedisExample(String[] args) {
try {
@Cleanup Jedis jedis = redisService.getJedis();
} catch (Exception ex) {
logger.error(“Jedis異常:”,ex)
}
//=>上面代碼相當(dāng)于如下:
Jedis jedis= null;
try {
jedis = redisService.getJedis();
} catch (Exception e) {
logger.error(“Jedis異常:”,ex)
} finally {
if (jedis != null) {
try {
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(4)@Getter/@Setter注解可以針對(duì)類的屬性字段自動(dòng)生成Get/Set方法累舷。
public class OrderCreateDemoReq{
@Getter
@Setter
private String customerId;
@Setter
@Getter
private String poolId;
//其他代碼……
}
//上面請(qǐng)求Req類的代碼相當(dāng)于如下:
public class OrderCreateDemoReq{
private String customerId;
private String poolId;
public String getCustomerId(){
return customerId;
}
public String getPoolId(){
return poolId;
}
public void setCustomerId(String customerId){
this.customerId = customerId;
}
public void setPoolId(String poolId){
this.pool = pool;
}
}
(5)@ToString注解,為使用該注解的類生成一個(gè)toString方法夹孔,默認(rèn)的toString格式為:ClassName(fieldName= fieleValue ,fieldName1=fieleValue)被盈。
@ToString(callSuper=true,exclude="someExcludedField")
public class Demo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
//上面代碼相當(dāng)于如下:
public class Demo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
@ Override
public String toString() {
return "Foo(super=" + super.toString() +
", someBoolean=" + someBoolean +
", someStringField=" + someStringField + ")";
}
}
(6)@EqualsAndHashCode注解析孽,為使用該注解的類自動(dòng)生成equals和hashCode方法。
@EqualsAndHashCode(exclude = {"id"}, callSuper =true)
public class LombokDemo extends Demo{
private int id;
private String name;
private String gender;
}
//上面代碼相當(dāng)于如下:
public class LombokDemo extends Demo{
private int id;
private String name;
private String gender;
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (!super.equals(o)) return false;
final LombokDemo other = (LombokDemo)o;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
return true;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + super.hashCode();
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
return result;
}
}
(7) @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor,這幾個(gè)注解分別為類自動(dòng)生成了無參構(gòu)造器只怎、指定參數(shù)的構(gòu)造器和包含所有參數(shù)的構(gòu)造器袜瞬。
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}
//上面代碼相當(dāng)于如下:
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
private ConstructorExample(T description) {
if (description == null) throw new NullPointerException("description");
this.description = description;
}
public static <T> ConstructorExample<T> of(T description) {
return new ConstructorExample<T>(description);
}
@java.beans.ConstructorProperties({"x", "y", "description"})
protected ConstructorExample(int x, int y, T description) {
if (description == null) throw new NullPointerException("description");
this.x = x;
this.y = y;
this.description = description;
}
public static class NoArgsExample {
@NonNull private String field;
public NoArgsExample() {
}
}
}
(8)@Data注解作用比較全,其包含注解的集合@ToString身堡,@EqualsAndHashCode邓尤,所有字段的@Getter和所有非final字段的@Setter, @RequiredArgsConstructor。其示例代碼可以參考上面幾個(gè)注解的組合贴谎。
(9)@Builder注解提供了一種比較推崇的構(gòu)建值對(duì)象的方式汞扎。
@Builder
public class BuilderExample {
private String name;
private int age;
@Singular private Set<String> occupations;
}
//上面代碼相當(dāng)于如下:
public class BuilderExample {
private String name;
private int age;
private Set<String> occupations;
BuilderExample(String name, int age, Set<String> occupations) {
this.name = name;
this.age = age;
this.occupations = occupations;
}
public static BuilderExampleBuilder builder() {
return new BuilderExampleBuilder();
}
public static class BuilderExampleBuilder {
private String name;
private int age;
private java.util.ArrayList<String> occupations;
BuilderExampleBuilder() {
}
public BuilderExampleBuilder name(String name) {
this.name = name;
return this;
}
public BuilderExampleBuilder age(int age) {
this.age = age;
return this;
}
public BuilderExampleBuilder occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.addAll(occupations);
return this;
}
public BuilderExampleBuilder clearOccupations() {
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public BuilderExample build() {
Set<String> occupations = new HashSet<>();
return new BuilderExample(name, age, occupations);
}
@verride
public String toString() {
return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
}
}
}
(10)@Synchronized注解類似Java中的Synchronized 關(guān)鍵字,但是可以隱藏同步鎖擅这。
public class SynchronizedExample {
private final Object readLock = new Object();
@Synchronized
public static void hello() {
System.out.println("world");
}
@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
//上面代碼相當(dāng)于如下:
public class SynchronizedExample {
private static final Object $LOCK = new Object[0];
private final Object readLock = new Object();
public static void hello() {
synchronized($LOCK) {
System.out.println("world");
}
}
public void foo() {
synchronized(readLock) {
System.out.println("bar");
}
}
}
Lombok背后的自定義注解原理
本文在前三章節(jié)主要介紹了Lombok這款Java開發(fā)利器中各種定義注解的使用方法澈魄,但作為一個(gè)Java開發(fā)者來說光了解插件或者技術(shù)框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技術(shù)原理仲翎,看明白源碼設(shè)計(jì)理念才能真正做到“知其然知其所以然”痹扇。好了,話不多說下面進(jìn)入本章節(jié)的正題溯香,看下Lombok背后注解的深入原理鲫构。
可能熟悉Java自定義注解的同學(xué)已經(jīng)猜到,Lombok這款插件正是依靠可插件化的Java自定義注解處理API(JSR 269: Pluggable Annotation Processing API)來實(shí)現(xiàn)在Javac編譯階段利用“Annotation Processor”對(duì)自定義的注解進(jìn)行預(yù)處理后生成真正在JVM上面執(zhí)行的“Class文件”玫坛。有興趣的同學(xué)反編譯帶有Lombok注解的類文件也就一目了然了结笨。其大致執(zhí)行原理圖如下:
從上面的這個(gè)原理圖上可以看出Annotation Processing是編譯器在解析Java源代碼和生成Class文件之間的一個(gè)步驟。其中Lombok插件具體的執(zhí)行流程如下:
從上面的Lombok執(zhí)行的流程圖中可以看出湿镀,在Javac 解析成AST抽象語法樹之后, Lombok 根據(jù)自己編寫的注解處理器禀梳,動(dòng)態(tài)地修改 AST,增加新的節(jié)點(diǎn)(即Lombok自定義注解所需要生成的代碼)肠骆,最終通過分析生成JVM可執(zhí)行的字節(jié)碼Class文件算途。使用Annotation Processing自定義注解是在編譯階段進(jìn)行修改,而JDK的反射技術(shù)是在運(yùn)行時(shí)動(dòng)態(tài)修改蚀腿,兩者相比嘴瓤,反射雖然更加靈活一些但是帶來的性能損耗更加大。
需要更加深入理解Lombok插件的細(xì)節(jié)莉钙,自己查閱其源代碼是必比可少的廓脆。對(duì)開源框架代碼比較有執(zhí)著追求的童鞋可以將Lombok的源代碼工程從github上download到本地進(jìn)行閱讀和自己調(diào)試。下圖為Lombok工程源代碼的截圖:
從熟悉JSR 269: Pluggable Annotation Processing API的同學(xué)可以從工程類結(jié)構(gòu)圖中發(fā)現(xiàn)AnnotationProcessor這個(gè)類是Lombok自定義注解處理的入口磁玉。該類有兩個(gè)比較重要的方法一個(gè)是init方法停忿,另外一個(gè)是process方法。在init方法中蚊伞,先用來做參數(shù)的初始化席赂,將AnnotationProcessor類中定義的內(nèi)部類(JavacDescriptor吮铭、EcjDescriptor)先注冊(cè)到ProcessorDescriptor類型定義的列表中。其中颅停,內(nèi)部靜態(tài)類—JavacDescriptor在其加載的時(shí)候就將 lombok.javac.apt.LombokProcessor
這個(gè)類進(jìn)行對(duì)象實(shí)例化并注冊(cè)谓晌。
在 LombokProcessor
處理器中,其中的process方法會(huì)根據(jù)優(yōu)先級(jí)來分別運(yùn)行相應(yīng)的handler處理類癞揉。Lombok中的多個(gè)自定義注解都分別有對(duì)應(yīng)的handler處理類纸肉,如下圖所示:
可以看出,在Lombok中對(duì)于其自定義注解進(jìn)行實(shí)際的替換喊熟、修改和處理的正是這些handler類柏肪。對(duì)于其實(shí)現(xiàn)的細(xì)節(jié)可以具體參考其中的代碼。
本文先從Lombok使用角度出發(fā)芥牌,先介紹了如何在當(dāng)前主流的Java開發(fā)環(huán)境—Intellij中安裝這塊Java插件预吆,隨后分別介紹了Lombok中幾種主要的常用注解(比如, @Data
胳泉、 @CleanUp
、 @ToString
和 @Getter/Setter
等)岩遗,最后將Lombok背后Java自定義注解的原理與源代碼結(jié)合起來扇商,介紹了Lombok插件背后具體的執(zhí)行處理流程。限于篇幅宿礁,未能對(duì)“自定義Java注解處理器”的具體實(shí)踐和原理進(jìn)行闡述案铺,后面將另起專題篇幅進(jìn)行介紹。限于筆者的才疏學(xué)淺梆靖,對(duì)本文內(nèi)容可能還有理解不到位的地方控汉,如有闡述不合理之處還望留言一起探討。