大家好,我是二哥呀!由于感冒嗓子哑了快一周时间,不能说话的感觉实在是太糟糕了!
Spring Boot 早在 2.1.x 版本后就在 starter 中内置了 Lombok 依赖,Intellij IDEA 也早在 IDEA 2020.3 版本的时候内置了 Lombok 插件。为什么它们都要支持 Lombok 呢?Lombok 到底有啥牛皮的?今天我们就来补上这一课。
Lombok
在官网是这样作自我介绍的:
Project Lombok makes java a spicier language by adding 'handlers' that know how to build and compile simple, boilerplate-free, not-quite-java code.
大致的意思就是:Lombok
是个好类库,可以为 Java 代码添加一些“处理程序”,让其变得更简洁、更优雅。在我看来,Lombok
最大的好处就在于通过注解的形式来简化 Java 代码,简化到什么程度呢?
作为一名 Java 程序员,我相信你一定写过不少的 getter / setter
,尽管可以借助 IDE 来自动生成,可一旦 Javabean
的属性很多,就免不了要产生大量的 getter / setter
,这会让代码看起来不够简练,就像老太婆的裹脚布一样,又臭又长。
class Cmower {
private int age;
private String name;
private BigDecimal money;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
}
Lombok
可以通过注解的方式,在编译的时候自动为 Javabean
的属性生成 getter / setter
,不仅如此,还可以生成构造方法、equals
方法、hashCode
方法,以及 toString
方法。注意是在编译的时候哦,源码当中是没有 getter / setter
等等的。
@Getter
@Setter
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
}
瞧一瞧,源码看起来苗条多了,对不对?
如果项目使用 Maven
构建的话,添加Lombok
的依赖就变得轻而易举了。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
其中 scope=provided
,就说明 Lombok
只在编译阶段生效。也就是说,Lombok
会在编译期静悄悄地将带 Lombok
注解的源码文件正确编译为完整的 class
文件。
SpringBoot 2.1.x 版本后不需要再显式地添加 Lombok 依赖了。之后,还需要为 Intellij IDEA 安装 Lombok
插件,否则 Javabean
的 getter / setter
就无法自动编译,也就不能被调用。不过,新版的 Intellij IDEA 也已经内置好了,不需要再安装。
1)**@Getter / @Setter**
@Getter / @Setter
用起来很灵活,比如说像下面这样:
class CmowerLombok {
@Getter @Setter private int age;
@Getter private String name;
@Setter private BigDecimal money;
}
字节码文件反编译后的内容是:
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
}
2)**@ToString**
打印日志的好帮手哦。
@ToString
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
}
字节码文件反编译后的内容是:
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
public String toString() {
return "CmowerLombok(age=" + this.age + ", name=" + this.name + ", money=" + this.money + ")";
}
}
3)val
在编写 JavaScript 代码时,我一直觉得 var
这个变量声明类型用起来特别方便。Lombok
也提供了一个类似的。
class CmowerLombok {
public void test() {
val names = new ArrayList<String>();
names.add("沉默王二");
val name = names.get(0);
System.out.println(name);
}
}
字节码文件反编译后的内容是:
class CmowerLombok {
public void test() {
ArrayList<String> names = new ArrayList();
names.add("沉默王二");
String name = (String) names.get(0);
System.out.println(name);
}
}
4)**@Data**
@Data 注解可以生成 getter / setter
、equals
、hashCode
,以及 toString
,是个总和的选项。
@Data
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
}
字节码文件反编译后的内容是:
class CmowerLombok {
private int age;
private String name;
private BigDecimal money;
public int getAge() {
return this.age;
}
public String getName() {
return this.name;
}
public BigDecimal getMoney() {
return this.money;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof CmowerLombok)) {
return false;
} else {
CmowerLombok other = (CmowerLombok) o;
if (!other.canEqual(this)) {
return false;
} else if (this.getAge() != other.getAge()) {
return false;
} else {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$money = this.getMoney();
Object other$money = other.getMoney();
if (this$money == null) {
if (other$money != null) {
return false;
}
} else if (!this$money.equals(other$money)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof CmowerLombok;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + this.getAge();
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
Object $money = this.getMoney();
result = result * 59 + ($money == null ? 43 : $money.hashCode());
return result;
}
public String toString() {
return "CmowerLombok(age=" + this.getAge() + ", name=" + this.getName() + ", money=" + this.getMoney() + ")";
}
}
5)@Slf4j
@Slf4j 可以用来生成注解对象,你可以根据自己的日志实现方式来选用不同的注解,比如说:@Log、@Log4j、@Log4j2、@Slf4j等。
@Slf4j
public class Log4jDemo {
public static void main(String[] args) {
log.info("level:{}","info");
log.warn("level:{}","warn");
log.error("level:{}", "error");
}
}
字节码文件反编译后的内容是:
public class Log4jDemo {
private static final Logger log = LoggerFactory.getLogger(Log4jDemo.class);
public Log4jDemo() {
}
public static void main(String[] args) {
log.info("level:{}", "info");
log.warn("level:{}", "warn");
log.error("level:{}", "error");
}
}
6)@Builder
@Builder 注解可以用来通过建造者模式来创建对象,这样就可以通过链式调用的方式进行对象赋值,非常的方便。
@Builder
@ToString
public class BuilderDemo {
private Long id;
private String name;
private Integer age;
public static void main(String[] args) {
BuilderDemo demo = BuilderDemo.builder().age(18).name("沉默王二").build();
System.out.println(demo);
}
}
字节码文件反编译后的内容是:
public class BuilderDemo {
private Long id;
private String name;
private Integer age;
public static void main(String[] args) {
BuilderDemo demo = builder().age(18).name("沉默王二").build();
System.out.println(demo);
}
BuilderDemo(final Long id, final String name, final Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public static BuilderDemo.BuilderDemoBuilder builder() {
return new BuilderDemo.BuilderDemoBuilder();
}
public String toString() {
return "BuilderDemo(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
}
public static class BuilderDemoBuilder {
private Long id;
private String name;
private Integer age;
BuilderDemoBuilder() {
}
public BuilderDemo.BuilderDemoBuilder id(final Long id) {
this.id = id;
return this;
}
public BuilderDemo.BuilderDemoBuilder name(final String name) {
this.name = name;
return this;
}
public BuilderDemo.BuilderDemoBuilder age(final Integer age) {
this.age = age;
return this;
}
public BuilderDemo build() {
return new BuilderDemo(this.id, this.name, this.age);
}
public String toString() {
return "BuilderDemo.BuilderDemoBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
}
}
}
除了我上面提到的这些,Lombok 还提供了同步注解 @Synchronized、自动抛出异常注解 @SneakyThrows、不可变对象 @Value、自动生成 hashCode 和 equals 方法的注解 @EqualsAndHashCode 等等,大家可以去尝试一下,顺带看一下反编译后的字节码,体验一下 Lombok 的工作原理。
一图胜千言,直接上图。
Lombok 用起来虽然爽,但需要团队内部达成一致,就是要用大家都用,否则有些用了有些没用就会乱成一锅粥,很影响代码的整体风格。另外,假如有团队成员还在用 Eclipse,那么也得要求他安装 Lombok 插件,否则打开一个使用 Lombok 注解的项目就会无法通过编译。
如果一类使用了 Lombok 注解,通过类结构是可以查看到对应的方法的,比如说下图中的 toString 和 builder 方法。
打开 target 目录下的 .class 文件,就可以看到 Lombok 生成的反编译后的字节码文件,也可以验证 Lombok 是在编译阶段实现 Java 代码增强功能的。
相信看完技术文章的小伙伴手气一定会更旺,简单介绍一下这本书《实战 Alibaba Sentinel——深度解析微服务高并发流量治理》:
本书分为14章,涵盖的知识主要包括:限流与熔断等基础概念、Sentinel的特性与性能压测,Sentinel概念、核心类与数据结构,Sentinel整体工作流程,资源指标数据统计,限流与流量效果控制,熔断降级与熔断器,授权与系统自适应功能,扩展Sentinel实现开关降级,Sentinel动态数据源,Sentinel适配主流框架的实现原理,热点参数限流,集群限流,异步调用链的支持,资源指标数据的收集与持久化。本书内容丰富,概念通俗易懂,让读者不仅能够深入理解Sentinel的实现原理,还能够从Sentinel中学习到一些技术,如Java SPI的应用、责任链设计模式的应用、高并发性能优化、滑动窗口的实现、匀速限流与冷启动算法、信号量隔离的目的与实现等。