大家好,我是一航!
周末的时候,和朋友闲聊,说最近在加班,赶进度,搞项目重构;然后跟我在吐槽,团队不让用Lombok
,还列了一堆的缺点说让代码变的"亚健康",本来进度就挺赶的,因为一些基础的代码,消磨着精力和时间,心挺累的...
我个人呢,是挺喜欢lombok
的,也一直推荐身边的朋友去使用;虽然很多人反对lombok
的朋友细数了他的各种罪状,但是仍然没有办法说服我放弃使用lombok
;至少,目前在我的团队,是适用的;
在讨论Lombok的各种问题之前,还是先还是来说说什么是Lombok以及日常用法,防止有朋友没有使用过;或者并没有使用到全部的功能,导致不知道在讲什么。
那么一起先来体验一下他的优势吧。
Lombok
什么是Lombok?
Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如getter() 、setter() 、 hashCode() 和 equals() 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。
基础使用
安装插件(必装)
导入依赖
代码语言:javascript复制<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<optional>true</optional>
</dependency>
使用lombok代码之前对象
代码语言:javascript复制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个注解就能完成上面那些冗长的方法;
代码语言:javascript复制@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
自动生成一个对象的构造器
代码语言:javascript复制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(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所生成的方法,几乎都是通用的,标准的那一批;作用、含义基本都很容易达成共识的,所以只要详细的理解了各注解的作用,在代码的理解上,也就不会成为问题;
那么最后,一起来投个票吧!