Lombok 有什么用
在我们实体 Bean 中有大量的 Getter/Setter 方法以及 toString, hashCode 等可能不会用到, 但是某些时候仍然需要复写; 在使用 Lombok 之后, 将由其来自动帮你实现代码生成. 注意, 其是在编译源码过程中, 帮你自动生成的. 就是说, 将极大减少你的代码总量.
Lombok 的官方地址: https://projectlombok.org/
使用 Lombok 时需要注意的点
在类需要序列化, 反序列化时或者需要详细控制字段时, 应该谨慎考虑是否要使用 Lombok, 因为在这种情况下容易出问题. 例如: Jackson,JSON 序列化
使用 Lombok 虽然能够省去手动创建 setter 和 getter 方法等繁琐事情, 但是却降低了源代码文件的可读性和完整性, 减低了阅读源代码的舒适度
使用 @Slf4j 还是 @Log4j 注解, 需要根据实际项目中使用的日志框架来选择.
Lombok 并非处处适用, 我们需要选择适合的地方使用 Lombok, 例如 pojo 是一个好地方, 因为 pojo 很单纯
Lombok 的安装
eclipse 安装 Lombok 步骤:
下载最新的 lombok.jar 包, 下载地址: https://projectlombok.org/download.html
进入 cmd 窗口, 切到 Lombok 下载的目录, 运行命令:
java -jar lombok.jar
, 会出现如下界面:
已经默认选好了 eclipse 安装目录 (这个可能是因为我只有一个盘, 如果没有默认选择, 可以自己点击下方 Specify location... 按钮选择 eclipse 安装目录), 点击图中红色箭头指向的按钮, 即可完成安装. 成功界面如下:
eclipse 安装目录下的 eclipse.INI 文件末尾已经加了一行内容 (这个路径因人而异, 和 eclipse 安装目录有关), 如下:
而且安装目录下也多了一个 lombok.jar
spring boot 集成 Lombok
先去 http://start.spring.io/ 在线生成一个 spring boot 项目脚手架, 导入 eclipse.
在 pom.xml 里添加 Lombok 依赖:
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.16.14</version>
- </dependency>
在 src/main/java/com/example/springbootlombok/entity 下新建一个 student.java 的 Java bean:
- package com.example.springbootlombok.entity;
- import lombok.Data;
- @Data
- public class Student {
- private String name;
- private int age;
- }
在 src/test/java/com/example/springbootlombok 下新建一个 TestEntity.java 的测试类:
- package com.example.springbootlombok;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
- import com.example.springbootlombok.entity.Student;
- import lombok.extern.slf4j.Slf4j;
- @RunWith(SpringRunner.class)
- @SpringBootTest
- @Slf4j
- public class TestEntity {
- Student student = new Student();
- @Test
- public void test() {
- student.setName("张三");
- student.setAge(12);
- log.info("测试结果:" + student.toString());
- }
- }
执行 JUnit 测试, 成功的话, 日志里会有打印
测试结果: Student(name = 张三, age=12)
, 至此, spring boot 已经成功集成 Lombok 了.
Lombok 常用注解
@NonNull
这个注解可以用在成员方法或者构造方法的参数前面, 会自动产生一个关于此参数的非空检查, 如果参数为空, 则抛出一个空指针异常, 举个例子:
编译前的代码:
- // 成员方法参数加上 @NonNull 注解
- public String getName(@NonNull Person p) {
- return p.getName();
- }
编译后的代码:
- public String getName(@NonNull Person p) {
- if (p == null) {
- throw new NullPointerException("person");
- }
- return p.getName();
- }
- @Cleanup
这个注解用在变量前面, 可以保证此变量代表的资源会被自动关闭, 默认是调用资源的 close() 方法, 如果该资源有其它关闭方法, 可使用 @Cleanup("methodName") 来指定要调用的方法, 就用输入输出流来举个例子:
编译前的代码:
- 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[1024];
- while (true) {
- int r = in.read(b);
- if (r == -1) break;
- out.write(b, 0, r);
- }
- }
编译后的代码:
- public static void main(String[] args) throws IOException {
- InputStream in = new FileInputStream(args[0]);
- try {
- OutputStream out = new FileOutputStream(args[1]);
- try {
- byte[] b = new byte[10000];
- while (true) {
- int r = in.read(b);
- if (r == -1) break;
- out.write(b, 0, r);
- }
- } finally {
- if (out != null) {
- out.close();
- }
- }
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
- @Getter/@Setter
这一对注解从名字上就很好理解, 用在成员变量前面, 相当于为成员变量生成对应的 get 和 set 方法, 同时还可以为生成的方法指定访问修饰符, 当然, 默认为 public, 直接来看下面的简单的例子:
编译前的代码:
- public class Programmer {
- @Getter
- @Setter
- private String name;
- @Setter(AccessLevel.PROTECTED)
- private int age;
- @Getter(AccessLevel.PUBLIC)
- private String language;
- }
编译后的代码:
- public class Programmer {
- private String name;
- private int age;
- private String language;
- public void setName(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- protected void setAge(int age) {
- this.age = age;
- }
- public String getLanguage() {
- return language;
- }
- }
这两个注解还可以直接用在类上, 可以为此类里的所有非静态成员变量生成对应的 get 和 set 方法.
@Getter(lazy=true)
如果 Bean 的一个字段的初始化是代价比较高的操作, 比如加载大量的数据; 同时这个字段并不是必定使用的. 那么使用懒加载机制, 可以保证节省资源.
懒加载机制, 是对象初始化时, 该字段并不会真正的初始化, 而是第一次访问该字段时才进行初始化字段的操作.
@ToString/@EqualsAndHashCode
这两个注解也比较好理解, 就是生成 toString,equals 和 hashcode 方法, 同时后者还会生成一个 canEqual 方法, 用于判断某个对象是否是当前类的实例, 生成方法时只会使用类中的非静态和非 transient 成员变量, 这些都比较好理解, 就不举例子了.
当然, 这两个注解也可以添加限制条件, 例如用 @ToString(exclude={"param1","param2"}) 来排除 param1 和 param2 两个成员变量, 或者用 @ToString(of={"param1","param2"}) 来指定使用 param1 和 param2 两个成员变量,@EqualsAndHashCode 注解也有同样的用法.
@NoArgsConstructor/@RequiredArgsConstructor /@AllArgsConstructor
这三个注解都是用在类上的, 第一个和第三个都很好理解, 就是为该类产生无参的构造方法和包含所有参数的构造方法, 第二个注解则使用类中所有带有 @NonNull 注解的或者带有 final 修饰的成员变量生成对应的构造方法. 当然, 和前面几个注解一样, 成员变量都是非静态的, 另外, 如果类中含有 final 修饰的成员变量, 是无法使用 @NoArgsConstructor 注解的.
三个注解都可以指定生成的构造方法的访问权限, 同时, 第二个注解还可以用 @RequiredArgsConstructor(staticName="methodName") 的形式生成一个指定名称的静态方法, 返回一个调用相应的构造方法产生的对象, 下面来看一个生动鲜活的例子:
编译前的代码:
- @RequiredArgsConstructor(staticName = "sunsfan")
- @AllArgsConstructor(access = AccessLevel.PROTECTED)
- @NoArgsConstructor
- public class Shape {
- private int x;
- @NonNull
- private double y;
- @NonNull
- private String name;
- }
编译后的代码:
- public class Shape {
- private int x;
- private double y;
- private String name;
- public Shape() {
- }
- protected Shape(int x, double y, String name) {
- this.x = x;
- this.y = y;
- this.name = name;
- }
- public Shape(double y, String name) {
- this.y = y;
- this.name = name;
- }
- public static Shape sunsfan(double y, String name) {
- return new Shape(y, name);
- }
- }
- @Data/@Value
@Data 注解综合了 @Getter/@Setter,@ToString,@EqualsAndHashCode 和 @RequiredArgsConstructor 注解, 其中 @RequiredArgsConstructor 使用了类中的带有 @NonNull 注解的或者 final 修饰的成员变量, 它可以使用 @Data(staticConstructor="methodName") 来生成一个静态方法, 返回一个调用相应的构造方法产生的对象.
@Value 注解和 @Data 类似, 区别在于它会把所有成员变量默认定义为 private final 修饰, 并且不会生成 set 方法.
@SneakyThrows
这个注解用在方法上, 可以将方法中的代码用 try-catch 语句包裹起来, 捕获异常并在 catch 中用 Lombok.sneakyThrow(e) 把异常抛出, 可以使用 @SneakyThrows(Exception.class) 的形式指定抛出哪种异常, 很简单的注解, 直接看个例子:
编译前的代码:
- public class SneakyThrows implements Runnable {
- @SneakyThrows(UnsupportedEncodingException.class)
- public String utf8ToString(byte[] bytes) {
- return new String(bytes, "UTF-8");
- }
- @SneakyThrows
- public void run() {
- throw new Throwable();
- }
- }
编译后的代码:
- public class SneakyThrows implements Runnable {
- @SneakyThrows(UnsupportedEncodingException.class)
- public String utf8ToString(byte[] bytes) {
- try {
- return new String(bytes, "UTF-8");
- } catch(UnsupportedEncodingException uee) {
- throw Lombok.sneakyThrow(uee);
- }
- }
- @SneakyThrows
- public void run() {
- try {
- throw new Throwable();
- } catch(Throwable t) {
- throw Lombok.sneakyThrow(t);
- }
- }
- }
- @Synchronized
这个注解用在类方法或者实例方法上, 效果和 synchronized 关键字相同, 区别在于锁对象不同, 对于类方法和实例方法, synchronized 关键字的锁对象分别是类的 class 对象和 this 对象, 而 @Synchronized 的锁对象分别是私有静态 final 对象 LOCK 和私有 final 对象 lock, 当然, 也可以自己指定锁对象, 例子也很简单, 往下看:
编译前的代码:
- public class Synchronized {
- 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 Synchronized {
- private static final Object $LOCK = new Object[0];
- 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");
- }
- }
- }
- @Log
这个注解用在类上, 可以省去从日志工厂生成日志对象这一步, 直接进行日志记录, 具体注解根据日志工具的不同而不同, 同时, 可以在注解中使用 topic 来指定生成 log 对象时的类名. 不同的日志注解总结如下 (上面是注解, 下面是编译后的代码):
- @CommonsLog
- ==> private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
- @JBossLog
- ==> private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
- @Log
- ==> private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
- @Log4j
- ==> private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
- @Log4j2
- ==> private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
- @Slf4j
- ==> private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
- @XSlf4j
- ==> private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
参考资料
Spring Boot 下的 lombok 安装以及使用简介
lombok 注解介绍
来源: https://segmentfault.com/a/1190000018528777