Lombok
Lombok 是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具,在我们项目开发中经常使用model,entity等类,绝大部分数据类类中都需要get、set、toString等方法,一般我们需要手动的添加这些属性,但是如果我们受到业务的变更,字段的添加,修改等操作,我们需要更改实体类,但是使用了Lombok插件,只要我们定义了变量,例如使用@Data属性,lombok会在编译的时候,自动加上get、set方法。
官方地址:https://projectlombok.org/ github地址:https://github.com/rzwitserloot/lombok
1、Lombok安装
File–>Settings–>Plugins (或者快捷键ctrl + alt + s) 在Marketplace中搜索Lombok,点击安装,成功后重启idea即可。

2、Lombok使用
(1)在pom.xml文件中引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
(2)在entity类中测试
在IDEA点击View-->Tool Windows-->Structure,或者 Alt + 7 可以查看类中所有的方法和属性。

三、Lombok插件常用注解
1、@Getter/@Setter
注解在属性上,自动生成生成setter/getter方法,final变量不包含,还可以指定访问范围

注解在类上面

2、@ToString
注解在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性

3、@EqualsAndHashCode
注解在类上,自动生成equals()方法和hashCode方法

4、@NoArgsConstructor
注解在类上,自动生成空参构造方法

5、@Data
注解在类上,相当于同时使用了@Setter+@Getter+@EqualsAndHashCode+@NoArgsConstructor+@ToString,对于POJO类十分有用,
@Data如下:

组合键@Setter+@Getter+@EqualsAndHashCode+@NoArgsConstructor+@ToString如图所示:

6、@Value
@Value 注解和 @Data 类似,区别在于它会把所有成员变量默认定义为 final 修饰,并且不会生成 set() 方法。

7、@NonNull
注解能够为方法或构造函数的参数提供非空检查。
public void notNullExample(@NonNull String string) { string.length(); } //=>相当于 public void notNullExample(@NonNull String string) { if (string == null) { throw new NullPointerException("string is marked non-null but is null"); } else { string.length(); } }
8、@Cleanup
能够自动释放资源
public class CleanupExample { public static void main(String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream(args[0]); @Cleanup OutputStream out = new FileOutputStream(args[1]); byte[] b = new byte[10000]; while (true) { int r = in.read(b); if (r == -1) break; out.write(b, 0, r); } } } //=>相当于 public class CleanupExample { public CleanupExample() { } public static void main(String[] args) throws IOException { FileInputStream in = new FileInputStream(args[0]); try { FileOutputStream out = new FileOutputStream(args[1]); try { byte[] b = new byte[10000]; while(true) { int r = in.read(b); if (r == -1) { return; } out.write(b, 0, r); } } finally { if (Collections.singletonList(out).get(0) != null) { out.close(); } } } finally { if (Collections.singletonList(in).get(0) != null) { in.close(); } } } }
9、@RequiredArgsConstructor, @AllArgsConstructor
这几个注解分别为类自动生成了无参构造器、指定参数的构造器和包含所有参数的构造器。
@RequiredArgsConstructor public class Teacher { @NonNull private Integer pId; private String pName; } //=>相当于 public class Teacher { @NonNull private Integer pId; private String pName; public Teacher(@NonNull final Integer pId) { if (pId == null) { throw new NullPointerException("pId is marked non-null but is null"); } else { this.pId = pId; } } } @AllArgsConstructor public class Teacher { @NonNull private Integer pId; private String pName; } //=>相当于 public class Teacher { @NonNull private Integer pId; private String pName; public Teacher(@NonNull final Integer pId, final String pName) { if (pId == null) { throw new NullPointerException("pId is marked non-null but is null"); } else { this.pId = pId; this.pName = pName; } } }
10、@Builder
用在类、构造器、方法上,为你提供复杂的builder APIs,声明实体,表示可以进行Builder方式初始化.
@Data
@Builder(toBuilder=true) //修改实体,要求实体上添加@Builder(toBuilder=true)
public class Teacher {
private int id;
private String name;
}
public class UserTest {
public static void main(String[] args) {
//@Builder注解赋值新对象
Teacher teacher = Teacher.builder().id(1).name("小黄").build();
System.out.println(teacher.getId());
//@Builder注解修改原对象的属性值
teacher = teacher.toBuilder().id(3).name("ss").build();
System.out.println(teacher.getId());
}
@SuperBuilder
1、如果想要使用父类的属性,需要在父类和子类的类前加:@SuperBuilder(toBuilder = true)
注意:必须指明toBuilder = true,因为toBuilder默认是false
2、如果不需要集成父类的属性,可以使用@Builder或者是直接写成@SuperBuilder(toBuilder = true)/@SuperBuilder
@Singular
这个注解和@Builder一起使用,为Builder生成字段是集合类型的add方法,字段名不能是单数形式,否则需要指定value值。

11、@Log (and friends)
把 @Log 放在你的类上(无论哪个适用于你使用的日志系统);然后,您有一个静态 final log字段,按照您使用的日志记录框架的常用规定方式进行初始化,然后您可以使用它来编写日志语句。
@CommonsLog Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @Flogger Creates private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); @JBossLog Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); @Log Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j(常用) Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @XSlf4j Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); @CustomLog Creates private static final *com.foo.your.Logger* log = *com.foo.your.LoggerFactory.createYourLogger*(LogExample.class);
代码示例
import lombok.extern.java.Log; import lombok.extern.slf4j.Slf4j; @Log public class LogExample { public static void main(String... args) { log.severe("Something's wrong here"); } } @Slf4j public class UserTest { public static void main(String... args) { log.error("Something else is wrong here"); } } //=>相当于 public class UserTest { private static final Logger log = LoggerFactory.getLogger(UserTest.class); public UserTest() { } public static void main(String... args) { log.error("Something else is wrong here"); } }
12、@With
with 使用场景就为克隆对象。修改一个值而保留其他值不变。
例如,如果您创建公共类Point {private final int x,y; },setter没有意义,因为这些字段是最终字段。 @With可以为您生成一个withX(int newXValue)方法,该方法将返回一个新点,该点具有x的提供值和y的相同值。
@With 可以使用在类上,也可以使用在成员变量上。加在类上相当于给所有成员变量 @With。 必须要包含全参数构造器
@AllArgsConstructor public class Lombok { @With private Integer x; @With private Integer y; @With private Integer z; } //=>相当于 public class Lombok { private Integer x; private Integer y; private Integer z; public Lombok(Integer x, Integer y, Integer z) { this.x = x; this.y = y; this.z = z; } public Lombok withX(Integer x) { return this.x == x?this:new Lombok(x, this.y, this.z); } public Lombok withY(Integer y) { return this.y == y?this:new Lombok(this.x, y, this.z); } public Lombok withZ(Integer z) { return this.z == z?this:new Lombok(this.x, this.y, z); } }
@Wither
提供了给final字段赋值的一种方法,idea报错提示,@wither已经被@with代替,插件已经不再支持该注解

13、@val 和 @var
基本不用
IDEA不能主动提示val重新赋值的错误,对于final直接修饰的变量,重新赋值的时候IDEA会出现红色下划线的错误提示。
val可以作为局部变量声明的类型,而不必编写实际类型。val注解将从初始化程序表达式中推断类型。局部变量也将成为最终变量。此功能仅适用于局部变量和foreach循环,不适用于字段(实体类的成员变量)。同时,初始化表达式是必需的。 var和val的差别在于,val修饰的局部变量没有被标记为final。
public static void main(String[] args) { val setVar = new HashSet<String>(); val listsVar = new ArrayList<String>(); val mapVar = new HashMap<String,String>(); //=>上面代码相当于如下: final Set<String> setVar2 = new HashSet<>(); final List<String> listsVar2 = new ArrayList<>(); final Map<String, String> maps2 = new HashMap<>(); }
14、@UtilityClass
工具类,在使用注释类型UtilityClass声明类后,会得到以下效果
import lombok.experimental.UtilityClass; @UtilityClass public class UserTest { private final int CONSTANT = 5; public int addSomething(int in){ return in + CONSTANT; } } //=>上面代码相当于如下: //类会标记成final最终类 public final class UserTest { private static final int CONSTANT = 5; //属性会标记成静态属性 //会生成一个私有的无参构造函数,并抛出一个UnsupportedOperationException异常 private UserTest(){ throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } //方法会标记成静态方法 public static int addSomething(int in){ return in + CONSTANT; } }
15、@FieldNameConstants(开发中)
默认生成一个常量,名称为大写字段名,值为字段名。
import lombok.experimental.FieldNameConstants; import lombok.AccessLevel; @FieldNameConstants; public class FieldNameConstantsExample { private String iAmAField; //final类型不生成 private int andSoAmI; @FieldNameConstants.Exclude private final int asAmI; } //=>上面代码相当于如下: public class FieldNameConstantsExample { private final String iAmAField; private final int andSoAmI; private final int asAmI; public static final class Fields { public static final String iAmAField = "iAmAField"; public static final String andSoAmI = "andSoAmI"; } }
16、@Delegate
它会该类生成一些列的方法,这些方法都来自与List接口

代理模式,把字段的方法代理给类,默认代理所有方法。其中参数types:指定代理的方法,参数excludes:和types相反

import lombok.experimental.Delegate; import java.util.Collection; import java.util.List; public class Teacher { private interface Add{ boolean add(String x); boolean addAll(Collection<?extends String> x); } private @Delegate(types = Add.class) List<String> strings; } //=>上面代码相当于如下: public class Teacher { private List<String> strings; public Teacher() { } public boolean add(final String x) { return this.strings.add(x); } public boolean addAll(final Collection<? extends String> x) { return this.strings.addAll(x); } private interface Add { boolean add(String x); boolean addAll(Collection<? extends String> x); } }
17、@SneakyThrows
用try{}catch{}捕捉异常
public class Teacher { @SneakyThrows(IOException.class) public String utf8ToString(byte[] bytes) { return new String(bytes,"UTF-8"); } } //=>上面代码相当于如下: public class Teacher { public String utf8ToString(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (IOException var3) { throw var3; } }
18、@Accessors
Accessor的中文含义是存取器,@Accessors用于配置getter和setter方法的生成结果,下面介绍三个属性:
(1)fluent
fluent设置为true,则getter和setter方法的方法名都是基础属性名,且setter方法返回当前对象。如下
@Data @Accessors(fluent = true) public class Teacher { private Integer id; private String name; } //=>生成gettter和setter方法体如下,方法体略: public Integer id() { return this.id; } public String name() { return this.name; } public Teacher id(final Integer id) { this.id = id; return this; } public Teacher name(final String name) { this.name = name; return this; }
(2)chain
chain设置为true,则setter方法返回当前对象。如下
@Data @Accessors(chain = true) public class Teacher { private Integer id; private String name; } //=>生成gettter和setter方法体如下,方法体略: public Integer getId() { return this.id; } public String getName() { return this.name; } public Teacher setId(final Integer id) { this.id = id; return this; } public Teacher setName(final String name) { this.name = name; return this; }
(3)prefix
prefix的中文含义是前缀,用于生成getter和setter方法的字段名会忽视指定前缀(遵守驼峰命名)。如下
@Data @Accessors(prefix = "p") public class Teacher { private Integer pId; private String pName; } //=>生成gettter和setter方法体如下,方法体略: public Integer getId() { return this.pId; } public String getName() { return this.pName; } public void setId(final Integer pId) { this.pId = pId; } public void setName(final String pName) { this.pName = pName; }
四、Lombok的自定义注解原理
Lombok这款插件正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。

从上面的这个原理图上可以看出Annotation Processing是编译器在解析Java源代码和生成Class文件之间的一个步骤。其中Lombok插件具体的执行流程如下:

从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST(抽象语法树),增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改。
浙公网安备 33010602011771号