Lombok 让代码“亚健康”?你真的用对了吗?
大家好,我是一航!
周末的时候,和朋友闲聊,说最近在加班,赶进度,搞项目重构;然后跟我在吐槽,团队不让用 Lombok
,还列了一堆的缺点说让代码变的"亚健康",本来进度就挺赶的,因为一些基础的代码,消磨着精力和时间,心挺累的...
我个人呢,是挺喜欢 lombok
的,也一直推荐身边的朋友去使用;虽然很多人反对 lombok
的朋友细数了他的各种罪状,但是仍然没有办法说服我放弃使用 lombok
;至少,目前在我的团队,是适用的;
在讨论Lombok的各种问题之前,还是先还是来说说什么是Lombok以及日常用法,防止有朋友没有使用过;或者并没有使用到全部的功能,导致不知道在讲什么。
那么一起先来体验一下他的优势吧。
Lombok
什么是Lombok?
Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如getter() 、setter() 、 hashCode() 和 equals() 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。
基础使用
-
安装插件(必装)
-
导入依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <optional>true</optional> </dependency>
-
使用lombok代码之前对象
public class User { private Long id; private String name; private Integer age; private String addr; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id.equals(user.id) && name.equals(user.name) && age.equals(user.age) && addr.equals(user.addr); } @Override public int hashCode() { return Objects.hash(id, name, age, addr); } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", addr='" + addr + '\'' + '}'; } }
-
使用Lombok
通过4个注解就能完成上面那些冗长的方法;
@Getter @Setter @ToString @EqualsAndHashCode public class User { private Long id; private String name; private Integer age; private String addr; }
同样,上面冗长的注解,也可以通过一个
@Data
注解来代替:
一下子是不是变的简洁多了...@Data public class User { private Long id; private String name; private Integer age; private String addr; }
注解说明
-
@Setter
自动生成属性的set方法
-
AccessLevel.NONE
表示不生成set方法,如果在类上,就是所有变量都没有set方法,如果在变量上,就指明单个变量没有set方法
@Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) private Integer age;
-
-
@Getter
自动生成属性的get方法
-
@EqualsAndHashCode
自动生成equals()、canEquals()和hashCode()方法
-
@ToString
自动生成toString方法
-
@NonNull
指明对象不允许为空,会自动在构造方法,成员方法上面加上非空的校验
-
@Builder
自动生成一个对象的构造器
public static void main(String[] args) { UserBuilder userBuilder = new UserBuilder(); User user = userBuilder.id(1L) .name("张三") .age(10) .addr("北京") .build(); }
注:加上
@Builder
注解之后,虽然能沟通构造器去构建对象,但是大部分框架都是采用的get/set进行取值/赋值;所以在使用建议同时加上@Getter/@Setter注解 -
@NoArgsConstructor
自动生成无参构造方法
-
@NoArgsConstructor(staticName = "getUser")
自动生成一个返回对象的静态方法,方法名称为staticName变量指定的名称
public static User getUser() { return new User(); }
-
-
@AllArgsConstructor
自动生成包含所有属性的构造方法
-
@RequiredArgsConstructor
自动生成包含必要参数的构造方法,也就是被
final
或@NonNull
修饰的变量 -
@Data
这个是使用频率非常高的一个注解,日常开发中的DTO对象,Entity对象普遍都会加上这个注解
这个是使用频率非常高的一个注解,@Data =
@Setter
+@Getter
+@ToString
+@EqualsAndHashCode
+@RequiredArgsConstructor
-
@Value
@Value =
@Getter
+@ToString
+@EqualsAndHashCode
+@RequiredArgsConstructor
需要注意的是,这个注解生成的代码是不包含set方法,是因为他会所有变量都使用
final
修饰,然后通过构造方法对所有变量进行赋值。 -
@Log
自动生成一个log静态常量,常用的注解之一
@Slf4j
private static final Logger log = LoggerFactory.getLogger(User.class);
-
更多日志注解
不同的日志框架对应的不同的注解
@CommonsLog private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(User.class); @JBossLog private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(User.class); @Log private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(User.class.getName()); @Log4j private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(User.class); @Log4j2 private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(User.class); @Slf4j private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(User.class); @XSlf4j private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(User.class);
-
-
@Cleanup
自动生成释放资源的代码,默认是调用资源的
close()
方法,也可以自己指定释放的方法,如:@Cleanup("shutDown")
; -
@Accessors
Accessor的中文含义是存取器,@Accessors用于配置getter和setter方法的生成结果
-
@Accessors(fluent = true)
fluent表示流畅的;默认为false,设置为true时,getter和setter方法全部和属性名同名,并且setter方法返回当前对象
-
@Accessors(chain = true)
chain表示链式的,默认为false,当设置为true时,所有的Setter方法名都以set开头并返回当前对象;
-
@Accessors(prefix = {"pre","my"})
去掉指定前缀;遵循驼峰命名的变量,通过prefix指定前缀的集合之后,setter方法将会自动去掉对应的前缀,如下图,preName变量的set方法为
setName()
;myAddr的set方法为setAddr()
-
-
@Synchronized
和
synchronized
关键词的用法差不多,加上@Synchronized
注解之后,会自动对方法类的内容使用synchronized
关键词包裹注:个人不太建议采用这个注解进行加锁,最主要的原因时粒度太粗了;所以需要酌情考虑是否真的合适!
所有的注解加起来,是不是能为我们日常开发省去很多的体力活?答案是肯定的,这也是他吸引了这么多人使用的最根本原因;
主要的问题
上面的示例以及涵盖了Lombok的绝大部分的使用场景;确实为日常开发带来了很多便捷,但这些优点的背后同样也衍生出了一些不得不说的问题:
强耦合、被迫使用
文章一开始就有说到,使用lombok必须安装一个插件,才能正常编译通过,如果是团队协作开发,某一天你加入了Lombok,其他人并没有使用,那么他在同步你的代码之后,需要被迫使用Lombok插件,否则编译无法通过;无形中增加了代码、工具的耦合度
如果你要做开发项目,我的建议是,老老实实把这个插件给卸载了吧,因为这可能直接影响到你开源项目的使用率;特别是插件类的开源项目。
阅读性差
源码的主要目的是为了:阅读;使用Lombok之后,相关的方法都是在编译器自动生成的,在源码上并没有体现出来,从而导致无法直观的看到具体的实现逻辑,增加了理解成本;
当对代码进行debug的时候,自动生成的代码无法进行逐行调试,增加了对问题的排查成本;
误用
举几个简单的例子
- 几个构造方法的注解,不同的注解有着不同的效果,不能一味的使用
@AllArgsConstructor
生成一个大而全的构造方法讲所有属性都对外暴露了,需要在不同的场景,使用不同的注解; @Synchronized
注解,如果方法中只有一小段代码需要加上锁,使用@Synchronized直接是将整个方法都加上了锁,从而导致粒度变粗,性能变差;- 比如对象只需要get方法,但是在使用过程中把
@Data
注解给安排上了,虽然get方法都有了,但同时也增加了所有的set方法也都给安排上了,无效中增加了代码的安全性问题。
这些使用,虽然在最终的结果上面,并没有带来什么大的影响,但是无形中产生了一些隐患,一旦隐患堆积多了之后,就会产生一种插件不好用的错觉。
技术债
如果是个人使用,Lombok的学习成本并不高,就那么十来个注解,可能花一会儿功夫就了解清楚了;但当团队决定使用Lombok的时候,就需要要求每一个成员都能够熟练的掌握,那最终带来的成本就是几何倍了;
总结
本文不仅列举出了Lombok详细的用法,还列举了其优缺点,同样这也正是赞成派和反对派各自所持不同的立场;凡事都具备两面性,不能完全从一个角度去钻牛角尖,那样本身就失去了技术创新的意义了;
优点就不过分的吹捧,缺点不过分的贬低
;最终的使用还得看是否真的合适;
至于不足的那些方方面面,也并不是完全无计可施;使用框架的最终目的是提高生产力,被迫使用
、技术债
这些问题,团队需要在一开始的时候就讨论清楚,并结合团队间的影响、学习成本等方面来权衡利弊,如果利大于弊,那就可以安排上,通过系统的学习、培训之后,自然也就不会出现 误用
这些问题了;
至于 阅读性差
;Lombok所生成的方法,几乎都是通用的,标准的那一批;作用、含义基本都很容易达成共识的,所以只要详细的理解了各注解的作用,在代码的理解上,也就不会成为问题;
那么最后,一起来投个票吧!