一、Lombok简介

  

  (1)Lombok官网(https://projectlombok.org/)对lombok的介绍

  (2)GitHub项目地址:https://github.com/rzwitserloot/lombok

  虽然是生硬的翻译,大家也大致可以看到Lombok存在的价值和意义,Lombok主要是可以提高开发效率,让我们这些小码农们工作时可以偷懒,让我们不再编写很多臃肿而定式的代码,虽然现在我们使用IDE工具可以生成很多,但是频繁的生成也会让我们的实体类看起来非常的臃肿。Lombok正是我们这些处于水深火热中的小码农的福音。

二、Lombok存在的意义:

  (1)简化冗余的JavaBean代码,使得实体文件很简洁;

  (2)便捷的生成比较复杂的代码,例如一个POJO要转化成构建器模式的形式,只需要一个注解。

三、Lombok有哪些注解,他们的作用分别是什么?

  a)Lombok的注解概览

  b)下面对每个注解进行一一总结,如下:

    1、@NotNull

    ①详细介绍:这个注解可以用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常。

    ②栗子:

    编译前:

//成员方法参数加上@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();
}

    2、@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();
}
}
}

    3、@Getter/@Setter

    ①、详细介绍:这一对注解从名字上就很好理解,用在成员变量前面,相当于为成员变量生成对应的get和set方法,同时还可以为生成的方法指定访问修饰符,当然,默认为public。也可以用在类上,表示为该类所有的属性,生成对应的get和set方法。

    ②、栗子:

    编译前:

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;
}
}

    4、@Getter(lazy=true)

    ①详细介绍:如果Bean的一个字段的初始化是代价比较高的操作,比如加载大量的数据;同时这个字段并不是必定使用的。那么使用懒加载机制,可以保证节省资源。懒加载机制,是对象初始化时,该字段并不会真正的初始化,而是第一次访问该字段时才进行初始化字段的操作。

    5、@ToString/@EqualsAndHashCode

     ①详细介绍:这两个注解也比较好理解,就是生成toString,equals和hashcode方法,同时后者还会生成一个canEqual方法,用于判断某个对象是否是当前类的实例,生成方法时只会使用类中的非静态和非transient成员变量,这些都比较好理解,就不举例子了。

    当然,这两个注解也可以添加限制条件,例如用@ToString(exclude={"param1","param2"})来排除param1和param2两个成员变量,或者用@ToString(of={"param1","param2"})来指定使用param1和param2两个成员变量,@EqualsAndHashCode注解也有同样的用法。

    6、@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);
}
}

    7、@Data/@Value

    ①、详细介绍:@Data注解综合了@Getter/@Setter,@ToString,@EqualsAndHashCode和@RequiredArgsConstructor注解,其中@RequiredArgsConstructor使用了类中的带有@NonNull注解的或者final修饰的成员变量,它可以使用@Data(staticConstructor="methodName")来生成一个静态方法,返回一个调用相应的构造方法产生的对象。

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

    8、@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);
}
}
}

    9、@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");
}
}
}

    10、@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);

    11、@Accessors

    ①、详细介绍:通过该注解可以控制getter和setter方法的形式。

    ②、栗子:

      a)fluent 若为true,则getter和setter方法的方法名都是属性名,且setter方法返回当前对象。

@Data
@Accessors(fluent = true)
class User {
private Integer id;
private String name; // 生成的getter和setter方法如下,方法体略
public Integer id(){}
public User id(Integer id){}
public String name(){}
public User name(String name){}
}

      b)chain 若为true,则setter方法返回当前对象。

@Data
@Accessors(chain = true)
class User {
private Integer id;
private String name; // 生成的setter方法如下,方法体略
public User setId(Integer id){}
public User setName(String name){}
}

      c)prefix 若为true,则getter和setter方法会忽视属性名的指定前缀(遵守驼峰命名)

@Data
@Accessors(prefix = "f")
class User {
private Integer fId;
private String fName; // 生成的getter和setter方法如下,方法体略
public Integer id(){}
public void id(Integer id){}
public String name(){}
public void name(String name){}
}

 

四、Lombok的实际应用:

  a)添加Lombok的maven依赖:

  maven依赖:

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>

  b)下载插件 Lombok Plugin(为什么要下载IDE插件,下文原理中会详解)

  c)只需要引入依赖、下载插件后,就可以直接在开发时使用啦

  使用Lombok开发:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; @Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String username;
private String password;
private Integer age;
private String description;
}

  不使用Lombok开发:

package com.shuwen.demo.entity;

public class User {
private String id;
private String username;
private String password;
private Integer age;
private String description;
public User(){}
public User(String id, String username, String password, Integer age, String description) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.description = description;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public String getDescription() {
return description;
} public void setDescription(String description) {
this.description = description;
} @Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", description='" + description + '\'' +
'}';
}
}

  经过这一对比,是不是很心动了呢,但是Lombok自身也有一定的缺陷,也需要注意哦~

五、Lombok的缺陷/易导致的错误

  通过官方文档,可以得知,当使用@Data注解时,则有了@EqualsAndHashCode注解,那么就会在此类中存在equals(Object other) 和 hashCode()方法,且不会使用父类的属性,这就导致了可能的问题。

  问题:比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。

六、Lombok的缺陷解决方案

  解决方案:

  (1)使用@Getter @Setter @ToString代替@Data并且自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。

  (2)或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解

七、Lombok的底层原理

  首先我们应该知道的是JSR 269:Pluggable Annotation Processing API:官网地址(https://www.jcp.org/en/jsr/detail?id=269)————可插入注释处理API,JSR 269 之前我们也有注解这样的神器,可是我们比如想要做什么必须使用反射,反射的方法局限性较大。

  首先,它必须定义:@Retention为RetentionPolicy.RUNTIME,只能在RUNTIME运行时通过反射来获取注解的值,这会导致运行时代码效率降低;其次,如果我们想在编码阶段利用注解来执行一些检查,对用户的某些不合格或者是不合理的代码进行告知,反射这种方法这时候就无法解决这样的问题。

  JSR 269如约而至,在它之后我们可以在JAVAC的编译期利用注解做这些事情,所以我们发现核心的区别还是注解的起作用时间到底是在运行期还是在编译期。

  简单介绍一下JSR 269(JDK6的新特性):

    如果存在标记处理器,并且编译参数里面指定要处理标记,那么这个过程就会处理在某个编译单元里面的标记。JSR269定义了一个接口,可以用来写这种Annotation处理插件。

  

  从上图可知,Annotation Processing 是在解析和生成之间的一个步骤。具体详细步骤如下:

  上图即是Lombok处理流程:

    (1)首先JAVAC解析成抽象语法树(AST);

    (2)然后Lombok根据自己的注解处理器,动态的修改AST,增加新的节点代码;

    (3)最后通过分析和生成字节码。

  自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

  IDE插件Lombok问题:

    举个栗子:

      现在有一个A类,其中有一些字段,没有创建它们的setter和getter方法,使用了lombok的@Data注解,另外有一个B类,它调用了A类实例的相应字段的setter和getter方法

      编译A类和B类所在的项目,并不会报错,因为最终生成的A类字节码文件中存在相应字段的setter和getter方法。

    但是,IDE有自己的想法,IDE发现B类源代码中所使用的A类实例的setter和getter方法在A类源代码中找不到定义,IDE会认为这是错误。

    所以我们为了让IDE不认为这是错误,让IDE放心,或者说是糊弄IDE都要下载安装Lombok Plugin插件。

  时光匆匆飞逝,望你我皆有所收获。

SpringBoot集成Lombok,应用+源码解析,让代码优雅起来的更多相关文章

  1. SpringBoot的条件注解源码解析

    SpringBoot的条件注解源码解析 @ConditionalOnBean.@ConditionalOnMissingBean 启动项目 会在ConfigurationClassBeanDefini ...

  2. SpringBoot 2.0.3 源码解析

    前言 用SpringBoot也有很长一段时间了,一直是底层使用者,没有研究过其到底是怎么运行的,借此机会今天试着将源码读一下,在此记录...我这里使用的SpringBoot 版本是  2.0.3.RE ...

  3. jquery源码解析:代码结构分析

    本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function() ...

  4. SpringBoot exception异常处理机制源码解析

    一.Spring Boot默认的异常处理机制 1:浏览器默认返回效果 2:原理解析 为了便于源码跟踪解析,在·Controller中手动设置异常. @RequestMapping(value=&quo ...

  5. SpringBoot自动配置的源码解析

    首先,写源码分析真的很花时间,所以希望大家转的时候也请注明一下,Thanks♪(・ω・)ノ SpringBoot最大的好处就是对于很多框架都默认的配置,让我们开发的时候不必为了大一堆的配置文件头疼,关 ...

  6. SpringBoot集成Swagger(根据源码深入学习Swagger的用法)

    从源码层面讲解Swagger的用法,快速了解掌握Swagger 简介 Swagger 是一个规范且完整的框架,用于生成.描述.调用和可视化 Restful 风格的 Web 服务. 自动生成html文档 ...

  7. axios 源码解析(中) 代码结构

    axios现在最新的版本的是v0.19.0,本节我们来分析一下它的实现源码,首先通过 gitHub地址获取到它的源代码,地址:https://github.com/axios/axios/tree/v ...

  8. yolo源码解析(1):代码逻辑

    一. 整体代码逻辑 yolo中源码分为三个部分,\example,\include,以及\src文件夹下都有源代码存在. 结构如下所示 ├── examples │ ├── darknet.c(主程序 ...

  9. Redux系列x:源码解析

    写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. ...

  10. Tensorflow版Faster RCNN源码解析(TFFRCNN) (2)推断(测试)过程不使用RPN时代码运行流程

    本blog为github上CharlesShang/TFFRCNN版源码解析系列代码笔记第二篇   推断(测试)过程不使用RPN时代码运行流程 作者:Jiang Wu  原文见:https://hom ...

随机推荐

  1. 多线程中操作UI

    遇到过要在工作线程中去更新UI以让用户知道进度,而在多线程中直接调用UI控件操作是错误的做法. 最后解决方法是将操作UI的代码封装,通过Invoke / BeginInvoke 去委托调用. 代码封装 ...

  2. 利用nginx解决cookie跨域

    一.写在前面 最近需要把阿里云上的四台服务器的项目迁移到客户提供的新的项目中,原来的四台服务器中用到了一级域名和二级域名.比如aaa.abc.com 和bbb.abc.com 和ccc.abc.com ...

  3. vue 使用踩坑 note

    1. 如图,假如large那一行错写成 'large': item.ext_data.isLarge + '' === 'true',, 那么,编译不报错,控制台无提示,模板不输出. 2. vue的t ...

  4. mysql 基础语法掌握~ This is just the beginning.

    create database 数据库名; drop database 数据库名; use 数据库名; create table table_name ( column_name, column_ty ...

  5. CSS position(定位)属性

    关于CSS position,来自MDN的描述: CSS position属性用于指定一个元素在文档中的定位方式.top.right.bottom.left 属性则决定了该元素的最终位置. 然后来看看 ...

  6. Codeforces Round #483 (Div. 2) D. XOR-pyramid

    D. XOR-pyramid time limit per test 2 seconds memory limit per test 512 megabytes input standard inpu ...

  7. MySql Query Cache 优化

    query cache原理 当mysql接收到一条select类型的query时,mysql会对这条query进行hash计算而得到一个hash值,然后通过该hash值到query cache中去匹配 ...

  8. JDK安装遇见的问题及解决方案

    问题描述: Jdk安装完成后从新启动电脑,打开eclipse报找 不到JRE或JDK,在cmd中输入  java -version 也不显示JDK信息. 后把JDK配置的环境变量path的JAVA_H ...

  9. js创建数组

    var a1 =  new Array(); var a2 =  new Array(7); var a3 =  new Array(100,"0",true); var a4 = ...

  10. 刨根问底HTTP和WebSocket协议

    HTML5的新成员:WebSocket 上篇介绍了HTTP1.1协议的基本内容,这篇文章将继续分析WebSocket协议,然后对这两个进行简单的比较. WebSocket WebSocket协议还很年 ...