Lombok使用

Lombok简介

Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。
官方地址
github地址

Lombok问题

无法支持多种参数构造器的重载

Lombok使用

1.添加依赖

  • maven
1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
  • gradle

https://projectlombok.org/setup/gradle

2.IDE中添加插件以支持Lombok

  • eclipse

https://projectlombok.org/setup/eclipse

  • idea

https://projectlombok.org/setup/intellij

3.常用注解

@Getter / @Setter

可以作用在类上和属性上,放在类上,会对所有的非静态(non-static)属性生成Getter/Setter方法,放在属性上,会对该属性生成Getter/Setter方法。并可以指定Getter/Setter方法的访问级别。

@EqualsAndHashCode

默认情况下,会使用所有非瞬态(non-transient)和非静态(non-static)字段来生成equals和hascode方法,也可以指定具体使用哪些属性。

@ToString

生成toString方法,默认情况下,会输出类名、所有属性,属性会按照顺序输出,以逗号分割。

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

无参构造器、部分参数构造器、全参构造器,当我们需要重载多个构造器的时候,Lombok就无能为力了。

@Data

包含@ToString, @EqualsAndHashCode, 所有属性的@Getter, 所有non-final属性的@Setter@RequiredArgsConstructor的组合,通常情况下,基本上使用这个注解就足够了。

@Value

@Data的变体,生成不可变类。生成的类是final的,且其中所有的属性默认情况下都是private final的,且不会再生成对应属性的setter。toString(), equals() 和 hashCode()方法会被生成。相当于下面代码:

1
final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter

@Slf4j

相当于在代码中引入logger

1
private static final Logger logger = LoggerFactory.getLogger(xxxService.class);

@Cleanup

自动的释放资源,例如流。相当于调用close()方法。

With Lombok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lombok.Cleanup;
import java.io.*;
public class CleanupExample {
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream zyc641701948@outlook.com = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
zyc641701948@outlook.com.write(b, 0, r);
}
}
}

Vanilla Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;
public class CleanupExample {
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream(args[0]);
try {
OutputStream zyc641701948@outlook.com = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
zyc641701948@outlook.com.write(b, 0, r);
}
} finally {
if (zyc641701948@outlook.com != null) {
zyc641701948@outlook.com.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
}
}

@SneakyThrows

简化抛出异常,处理异常的方法。见示例。

With Lombok

1
2
3
4
5
6
7
8
9
10
11
12
13
import lombok.SneakyThrows;
public class SneakyThrowsExample implements Runnable {
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
@SneakyThrows
public void run() {
throw new Throwable();
}
}

Vanilla Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import lombok.Lombok;
public class SneakyThrowsExample implements Runnable {
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Lombok.sneakyThrow(e);
}
}
public void run() {
try {
throw new Throwable();
} catch (Throwable t) {
throw Lombok.sneakyThrow(t);
}
}
}

@Synchronized

https://projectlombok.org/features/Synchronized

synchronized的安全的变体。和synchronized一样,只能用在静态方法或者实例方法上。但是在实现方式上不同,此注解更安全,详见文档。

val

duck type

@NonNull

顾名思义

4.其他一些高效使用方法

bean中的链式风格

什么是链式风格?我来举个例子,看下面这个Student的bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public Student setName(String name) {
this.name = name;
return this;
}
public int getAge() {
return age;
}
public Student setAge(int age) {
return this;
}
}

仔细看一下set方法,这样的设置便是chain的style,调用的时候,可以这样使用:

1
2
3
Student student = new Student()
.setAge(24)
.setName("zs");

相信合理使用这样的链式代码,会更多的程序带来很好的可读性,那看一下如果使用lombok进行改善呢,请使用 @Accessors(chain = true),看如下代码:

1
2
3
4
5
6
7
@Accessors(chain = true)
@Setter
@Getter
public class Student {
private String name;
private int age;
}

这样就完成了一个对于bean来讲很友好的链式操作。

静态构造方法

静态构造方法的语义和简化程度真的高于直接去new一个对象。比如new一个List对象,过去的使用是这样的:

1
List<String> list = new ArrayList<>();

看一下guava中的创建方式:

1
List<String> list = Lists.newArrayList();

再回过头来看刚刚的Student,很多时候,我们去写Student这个bean的时候,他会有一些必输字段,比如Student中的name字段,一般处理的方式是将name字段包装成一个构造方法,只有传入name这样的构造方法,才能创建一个Student对象。

接上上边的静态构造方法和必传参数的构造方法,使用lombok将更改成如下写法(@RequiredArgsConstructor@NonNull):

1
2
3
4
5
6
7
8
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "ofName")
public class Student {
@NonNull private String name;
private int age;
}

测试代码:

1
Student student = Student.ofName("zs");

这样构建出的bean语义是否要比直接new一个含参的构造方法(包含 name的构造方法)要好很多。

当然,看过很多源码以后,我想相信将静态构造方法ofName换成of会先的更加简洁:

1
2
3
4
5
6
7
8
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "of")
public class Student {
@NonNull private String name;
private int age;
}

测试代码:

1
Student student = Student.of("zs");

当然他仍然是支持链式调用的:

1
Student student = Student.of("zs").setAge(24);

这样来写代码,真的很简洁,并且可读性很强。

使用builder

Builder模式我不想再多解释了,读者可以看一下《Head First》(设计模式) 的建造者模式。

今天其实要说的是一种变种的builder模式,那就是构建bean的builder模式,其实主要的思想是带着大家一起看一下lombok给我们带来了什么。

看一下Student这个类的原始builder状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static Builder builder(){
return new Builder();
}
public static class Builder{
private String name;
private int age;
public Builder name(String name){
this.name = name;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Student build(){
Student student = new Student();
student.setAge(age);
student.setName(name);
return student;
}
}
}

调用方式:

1
Student student = Student.builder().name("zs").age(24).build();

这样的builder代码,让我是在恶心难受,于是我打算用lombok重构这段代码:

1
2
3
4
5
@Builder
public class Student {
private String name;
private int age;
}

调用方式:

1
Student student = Student.builder().name("zs").age(24).build();

代理模式

正如我们所知的,在程序中调用rest接口是一个常见的行为动作,如果你和我一样使用过spring 的RestTemplate,我相信你会我和一样,对他抛出的非http状态码异常深恶痛绝。

所以我们考虑将RestTemplate最为底层包装器进行包装器模式的设计:

1
2
3
4
5
6
7
8
9
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
//实现RestOperations所有的接口
}

然后再由扩展类对FilterRestTemplate进行包装扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ExtractRestTemplate extends FilterRestTemplate {
private RestTemplate restTemplate;
public ExtractRestTemplate(RestTemplate restTemplate) {
super(restTemplate);
this.restTemplate = restTemplate;
}
public <T> RestResponseDTO<T> postForEntityWithNoException(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RestResponseDTO<T> restResponseDTO = new RestResponseDTO<T>();
ResponseEntity<T> tResponseEntity;
try {
tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables);
restResponseDTO.setData(tResponseEntity.getBody());
restResponseDTO.setMessage(tResponseEntity.getStatusCode().name());
restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue());
}catch (Exception e){
restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR);
restResponseDTO.setMessage(e.getMessage());
restResponseDTO.setData(null);
}
return restResponseDTO;
}
}

包装器ExtractRestTemplate很完美的更改了异常抛出的行为,让程序更具有容错性。在这里我们不考虑ExtractRestTemplate完成的功能,让我们把焦点放在FilterRestTemplate上,“实现RestOperations所有的接口”,这个操作绝对不是一时半会可以写完的,当时在重构之前我几乎写了半个小时,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
return restTemplate.getForObject(url,responseType);
}
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
return restTemplate.getForEntity(url,responseType,uriVariables);
}
//其他实现代码略。。。
}

我相信你看了以上代码,你会和我一样觉得恶心反胃,后来我用lombok提供的代理注解优化了我的代码(@Delegate):

1
2
3
4
5
@AllArgsConstructor
public abstract class FilterRestTemplate implements RestOperations {
@Delegate
protected volatile RestTemplate restTemplate;
}

这几行代码完全替代上述那些冗长的代码。

如果你喜欢我的blog,请鼓励我一杯咖啡☕️
0%