——springboot依赖lombok插件、lombok常用注解
官方介绍如下:
意思是:lombok是一个能自动插入到编辑器和构建工具的java库。目的是为了简化java代码,开发者不需要写setter、getter和构造方法等,只需要几个或多个lombok注解就能实现。
Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的pojo,都需要花时间去添加相应的getter、setter、构造器和equals等方法,当属性多时会出现大量的getter/setter方法,维护起来很麻烦,也容易出错。
springboot中只需要依赖lombok的jar包就可使用Lombok。
(1)方法一:可以在官网(Download)下载jar包:
(2)方法二: 可以使用maven添加依赖:
org.projectlombok
lombok
1.18.22
provided
@NotNull注解如果设置在一个属性上,lombok将在自动生成的方法/构造函数体的开头插入一个空检查,抛出NullPointerException,并将参数的名称作为message。
lombok注解实现:
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}
等价代码:
public class ConstructorExample {
private int x, y;
@NonNull private T description;
private ConstructorExample(T description) {
//空检查
if (description == null) throw new NullPointerException("description");
this.description = description;
}
public static ConstructorExample of(T description) {
return new ConstructorExample(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() {
}
}
}
生成不带参数的构造方法,即:无参构造方法。@NoArgsConstructor等价于:
public MessageInfo() {
}
如果存在被final修饰的属性,那么会编译失败。
可以设置force属性值为true,来避免编译错误。但是所有final修饰的属性会被初始化为final字段初始化为:0 或null 或 false。也就是说包装类型会被初始化为null,简单类型初始化为0或false。
只为final / @non-null修饰字段(不包括static修饰的)生成带参数的构造方法。
描述:为所有字段(包括final修饰的,不包括static修饰的)生成带参数的构造方法,即:全参数构造方法。
lombok官方文档的描述:
lombok注解实现:
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
* @return The current value of this person's age. Circles are round.
*/
@Getter @Setter private int age = 10;
/**
* Name of the person.
* -- SETTER --
* Changes the name of this person.
*
* @param name The new value.
*/
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
等价代码:
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*/
private int age = 10;
/**
* Name of the person.
*/
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
/**
* Age of the person. Water is wet.
*
* @return The current value of this person's age. Circles are round.
*/
public int getAge() {
return age;
}
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
*/
public void setAge(int age) {
this.age = age;
}
/**
* Changes the name of this person.
*
* @param name The new value.
*/
protected void setName(String name) {
this.name = name;
}
}
@Getter为非静态属性(non-static属性)生成getter方法;如果@Getter放在类上,则会为该类中所有非静态属性(non-static属性)生成getter方法。
@Setter为非静态(non-static属性)和非final修饰的属性生成setter方法;如果@Setter放在类上,则会为该类中所有非静态(non-static属性)和非final修饰的属性生成setter方法。
为所有对象生成toString方法,该方法会打印对象的所有属性值。默认情况下,它将打印类名,以及按顺序打印每个字段(static静态属性除外),字段直接用逗号分隔。示例如下:
MessageInfo(msgId=1, content=null, count=null, enabled=null, remark=备注)
参数:
指定不打印的属性列表(static静态属性除外)。需要搭配@ToString使用。
指定要打印的属性列表(可以放在static静态属性上)。需要搭配@ToString使用。
Lombok实现:
import lombok.ToString;
@ToString
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
@ToString.Exclude private int id;
public String getName() {
return this.name;
}
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
等价代码:
import java.util.Arrays;
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public String toString() {
return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
@Override public String toString() {
return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
}
}
(1)@SneakyThrows注解原理:
在java的异常体系中Exception异常有两种类型:
1)编译时异常:普通Exception类,如:UnsupportedEncodingException、IOException
等。
2)运行时异常:RuntimeException类,如:NullPointerException 空指针异常、
IndexOutOfBoundsException 数组越界异常等。
第一种会强制要求抛出它的方法声明throws,调用者必须显式地去处理(try..catch)这个异常。设计的目的是为了提醒开发者处理一些场景中必然可能存在的异常情况。比如网络异常造成IOException。所有的运行时异常不需要捕获,编译时异常是一定要捕获,否则编译会报错。
但是编写代码时,大部分情况下的异常,我们都是一路往外抛了事。所以渐渐的java程序员处理Exception的常见手段就是外面包一层RuntimeException,接着往上丢。这种解决思想尤其在Spring中到处出现。@SneakyThrows就是利用了这一机制,将当前方法抛出的异常,包装成RuntimeException,骗过编译器,使得调用点可以不用显示处理异常信息。
处理异常的一般方式如下:
try{
//do work
}catch(Exception e){
throw new RuntimeException(e);
}
通过try...catch来捕获异常,并把异常封装成运行时异常后继续往上层抛去。代码中try...catch越多,代码就越臃肿。Lombok的@SneakyThrows注解就是为了消除这样的模板代码。
lombok注解实现:
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();
}
}
等价代码:
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);
}
}
}
显然将异常封装为运行时异常RuntimeException的关键藏在Lombok.sneakyThrow(t);中。可能大家都会以为这个方法就是new RuntimeException()之类的。然而事实并非如此。阅读代码可以看出整个方法其实最核心的逻辑是throw (T)t;,利用泛型将我们传入的Throwable强转为RuntimeException。
public static RuntimeException sneakyThrow(Throwable t) {
if (t == null) throw new NullPointerException("t");
return Lombok.sneakyThrow0(t);
}
}
private static T sneakyThrow0(Throwable t) throws T {
throw (T)t;
}
那么问题来了,为什么这个地方可以对原来的异常进行强转为RuntimeExcption?以下为直接强转的代码,显然运行之后报类型转换异常。
private void sneakyThrowsTest() {
try {
throw new Exception();
} catch (Throwable e) {
// 直接将e强转为RuntimeException,运行到这里会报类型转换异常。
throw (RuntimeException)e;
}
}
实际上,这种做法是一种通过泛型欺骗了编译器,让编译器在编译期不报错,而最后在JVM虚拟机中执行的字节码的并没有区别编译时异常和运行时异常,只有是不是和抛不抛异常而已。
(2)@SneakyThrows和@SneakyThrows(UnsupportedEncodingException.class)的区别:
1)@SneakyThrows默认情况下会自动捕获Throwable类及其子类异常(也就是所有异常),然后封装成RuntimeException后抛出;
2)@SneakyThrows(UnsupportedEncodingException.class) 或 @SneakyThrows(value = {IOException.class, UnsupportedEncodingException.class}):表示指定捕获某几种异常(包括子类异常)然后封装成RuntimeException后抛出,其他异常不处理。
作用同synchronized关键字,为防止并发访问实例对象方法或类静态方法。该注解需放在方法上。
@Synchronized是synchronized方法修饰符的一个更安全的变体。与synchronized一样,注释只能用于静态方法和非静态方法。它的操作类似于synchronized关键字,但是又略有不同。不同点:修饰静态方法时,@Synchronized的锁是对象锁($LOCK);Synchronize关键字的锁是类锁。修饰非静态方法时,@Synchronized的锁是对象锁($lock);Synchronize关键字的锁是对象锁(this)。关键字锁定了这个字段,但是注释锁定了一个名为$lock的字段,该字段是私有的。注意,@Synchronized的锁是一个私有的(private)
当然,我们可以在代码中手动创建$LOCK或$lock锁对象,如果我们定义了锁对象,那么lombok就不会自动创建$LOCK或$lock锁对象。
我们也可以通过@Synchronized("锁对象名")方式来指定锁对象的名称。但是千万要注意,如果我们指定锁对象的名称,那么我们必须要手动定义该锁对象,lombok不会为我们创建该锁对象!
Lombok注解实现:
import lombok.Synchronized;
public class SynchronizedExample {
//自定义的锁对象
private final Object readLock = new Object();
@Synchronized
public static void hello() {
System.out.println("world");
}
@Synchronized
public int answerToLife() {
return 42;
}
@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
}
等价代码:
public class SynchronizedExample {
//lombok自动生成的锁对象$LOCK
private static final Object $LOCK = new Object[0];
//lombok自动生成的锁对象$lock
private final Object $lock = new Object[0];
//自定义的锁对象
private final Object readLock = new Object();
public static void hello() {
synchronized($LOCK) {
System.out.println("world");
}
}
public int answerToLife() {
synchronized($lock) {
return 42;
}
}
public void foo() {
synchronized(readLock) {
System.out.println("bar");
}
}
}
根据对象的所有属性,生成equals和hashCode方法。
@Builder注解可以为你的类生成复杂的构建器apis,自动生成所需的代码,使类可以用下面类似的代码实例化,例如:
Person.builder()
.name("Adam Savage")
.city("San Francisco")
.job("Mythbusters")
.job("Unchained Reaction")
.build();
如果@Builder放在类上了,那么就会自动生成一个私有全参构造函数,并将所有字段作为参数(就好像类上加@AllArgsConstructor(access = AccessLevel.PRIVATE))。一般情况下@Builder搭配@Getter使用。
一个带有@Builder注解的方法会生成以下7个东西:
lombok注解实现:
@Builder
class Example {
private T foo;
private final String bar;
}
等价代码:
class Example {
private T foo;
private final String bar;
private Example(T foo, String bar) {
this.foo = foo;
this.bar = bar;
}
public static ExampleBuilder builder() {
return new ExampleBuilder();
}
public static class ExampleBuilder {
private T foo;
private String bar;
private ExampleBuilder() {}
public ExampleBuilder foo(T foo) {
this.foo = foo;
return this;
}
public ExampleBuilder bar(String bar) {
this.bar = bar;
return this;
}
@java.lang.Override public String toString() {
return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
}
public Example build() {
return new Example(foo, bar);
}
}
}
@Data注解等价于:@Getter + @Setter + @RequiredArgsConstructor + @ToString + @EqualsAndHashCode。
@Data为所有字段生成getter、构造函数、toString、hashCode和equals方法,检查所有@NotNull约束字段。也会为所有非final字段生成setter方法。具体详见等价的注解。
@Value注解等价于: @Getter+ @AllArgsConstructor + @ToString + @EqualsAndHashCode + @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE)
@Value是@Data的不可变变量。默认情况下,所有属性字段都被设置为private和final,类本身也被设置为final,并且不会为属性生成setter方法。像@Data一样,还生成了有用的toString()、equals()和hashCode()方法,并为每个字段生成getter方法,还生成了覆盖每个参数的构造函数(除了在字段声明中初始化的final字段)。
lombok注解实现:
import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
import lombok.experimental.With;
import lombok.ToString;
@Value public class ValueExample {
String name;
@With(AccessLevel.PACKAGE) @NonFinal int age;
double score;
protected String[] tags;
@ToString(includeFieldNames=true)
@Value(staticConstructor="of")
public static class Exercise {
String name;
T value;
}
}
等价代码:
import java.util.Arrays;
public final class ValueExample {
private final String name;
private int age;
private final double score;
protected final String[] tags;
@java.beans.ConstructorProperties({"name", "age", "score", "tags"})
public ValueExample(String name, int age, double score, String[] tags) {
this.name = name;
this.age = age;
this.score = score;
this.tags = tags;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample)) return false;
final ValueExample other = (ValueExample)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
result = result * PRIME + this.getAge();
final long $score = Double.doubleToLongBits(this.getScore());
result = result * PRIME + (int)($score >>> 32 ^ $score);
result = result * PRIME + Arrays.deepHashCode(this.getTags());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
}
ValueExample withAge(int age) {
return this.age == age ? this : new ValueExample(name, age, score, tags);
}
public static final class Exercise {
private final String name;
private final T value;
private Exercise(String name, T value) {
this.name = name;
this.value = value;
}
public static Exercise of(String name, T value) {
return new Exercise(name, value);
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample.Exercise)) return false;
final Exercise> other = (Exercise>)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
final Object this$value = this.getValue();
final Object other$value = other.getValue();
if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
final Object $value = this.getValue();
result = result * PRIME + ($value == null ? 43 : $value.hashCode());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
}
}
}
至此,根据官方文档,详细通过示例介绍了lombok轻量级插件的使用。