LomboK

LomboK介绍和配置

什么是LomboK

​ Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。

​ 官网: https://www.projectlombok.org/

Lombok的作用

​ 通过添加注解的方式,Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。

​ 例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误,使代码看起来更简洁些。

Lombok的配置

  • 添加maven依赖
1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
  • 安装插件

    ​ 使用Lombok还需要插件的配合,我使用开发工具为idea. 打开idea的设置,点击Plugins,点击Browse repositories,在弹出的窗口中搜索lombok,然后安装即可

    image-20191121092349714

  • 解决编译时出错问题

    ​ 编译时出错,可能是没有enable注解处理器。Annotation Processors > Enable annotation processing。设置完成之后程序正常运行。

    image-20191121092543928

小结

  1. Lombox: 就是一个工具, 简化java代码开发
  2. Lombok环境
    • 添加坐标
    • 添加插件

Lombok注解

@Data

​ @Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class User implements Serializable{
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;
}

@Getter/@Setter

​ 如果觉得@Data太过残暴不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法.

1
2
3
4
5
6
7
8
9
10
11
12
public class User implements Serializable{
@Setter
@Getter
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;
}

如果想要自己显示的设置set方法(加一些判断逻辑什么的,也是可以的)。

lombok不会再生成响应set方法。

image-20220717133513431

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Page 是分页的模型对象
* @param <T> 是具体的模块的 javaBean 类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Page<T> {
public static final Integer PAGE_SIZE_DEFAULT = 4; // 每页默认显示条数

private Integer pageNo; // 当前页码
@Builder.Default
private Integer pageSize = PAGE_SIZE_DEFAULT; // 当前页显示数量
private Integer pageTotal; // 总页码数
private Integer pageTotalCount; // 总记录数
private List<T> items; // 当前页数据

public void setPageNo(Integer pageNo) {
// 边界值有效检查
this.pageNo = pageNo < 1 ? 1 : pageNo > pageTotal ? pageTotal : pageNo;
}
}

看图标,暗的

image-20220717133535344

@ToString

​ 类使用@ToString注解,Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。 通过exclude属性指定忽略字段不输出,

1
2
3
4
5
6
7
8
9
10
11
12
@ToString(exclude = {"id"}) 
public class User implements Serializable{
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;
}

@xxxConstructor

  • @NoArgsConstructor: 无参构造器
1
2
3
4
5
6
7
8
9
10
11
@NoArgsConstructor
public class User implements Serializable{
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;
}
  • @AllArgsConstructor: 全参构造器
1
2
3
4
5
6
7
8
9
10
11
@AllArgsConstructor
public class User implements Serializable{
private Integer id;
private String username;
private String password;
private String address;
private String nickname;
private String gender;
private String email;
private String status;
}

这三个注解都是用在类上的,第一个和第三个都很好理解,就是为该类产生无参的构造方法和包含所有参数的构造方法,第二个注解则使用类中所有带有@NonNull注解的或者带有final修饰的成员变量生成对应的构造方法,当然,和前面几个注解一样,成员变量都是非静态的,另外,如果类中含有final修饰的成员变量,是无法使用@NoArgsConstructor注解的。

三个注解都可以指定生成的构造方法的访问权限,同时,第二个注解还可以用@RequiredArgsConstructor(staticName=”methodName”)的形式生成一个指定名称的静态方法,返回一个调用相应的构造方法产生的对象,下面来看一个生动鲜活的例子:

1
2
3
4
5
6
7
8
9
10
@RequiredArgsConstructor(staticName = "sunsfan")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
public class Shape {
private int x;
@NonNull
private double y;
@NonNull
private String name;
}

实际效果相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
}

@builder

案例:在派生类(子类)中使用@Builder

父类(用@AllArgsConstructor注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 实体类的基类
* @author nbchen
* @date 2019/08/03
*/
@Data
@AllArgsConstructor
public class BaseEntity implements Serializable {

    /**
     * id
     */
    private Long id;

    /**
     * 创建时间
     */
    private Date created;

    /**
     * 修改时间
     */
    private Date updated;
}

子类(提供完整的构造方法,并添加@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
/**
* 用户实体
* @author nbchen
* @date 2019/08/03
*/
@Data
public class TbUser extends BaseEntity {

    /**
     * 用户名
      */
    private String username;

    /**
     * 密码,加密存储
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 注册邮箱
     */
    private String email;

    @Builder
    public TbUser(Long id, Date created, Date updated, String username, String password, String phone, String email) {
        super(id, created, updated);
        this.username = username;
        this.password = password;
        this.phone = phone;
        this.email = email;
    }
}

泛型实体使用Lombok builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Page 是分页的模型对象
* @param <T> 是具体的模块的 javaBean 类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Page<T> {
public static final Integer PAGE_SIZE_DEFAULT = 4; // 每页默认显示条数


private Integer pageNo; // 当前页码
@Builder.Default
private Integer pageSize = PAGE_SIZE_DEFAULT; // 当前页显示数量
private Integer pageTotal; // 总页码数
private Integer pageTotalCount; // 总记录数
private List<T> items; // 当前页数据
}

使用:

1
2
3
4
5
6
7
Page.<Book>builder()
.pageNo(pageNo) // 设置当前页码
.pageSize(pageSize) // 设置每页显示条数
.pageTotalCount(pageTotalCount) // 设置总记录数
.pageTotal(pageTotal) // 设置总页码数
.items(items) // 设置当前页数据
.build();

@Builder.Default

实体

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
// 图书类
public class Book implements Serializable {
    private int id; // id
    private String name; // 名称
    private BigDecimal price; // 价格
    private String author; // 作者
    private Integer sales; // 销量
    private Integer stock; // 库存
    private String img_path = "static/img/default.jpg"; // 图书默认地址

使用

1
2
3
4
5
6
@Test
public void addBook() {
    Book book = Book.builder().name("陈志杰自传").author("陈志杰").price(new BigDecimal("100.00")).sales(10000).stock(0).img_path(null).build();
    System.out.println("book = " + book);
    bookDao.addBook(book);
}

默认值这个时候不生效,会被null覆盖。

image-20230927190651674

原因: 

image-20230927190630680

@Builder将忽略类中的默认值,如果想要保留默认值那么添加@Builder.Default注解或者声明为final

声明final并不合适的,添加@Builder.Default是解决办法。

一开始我是手动写判断,笨拙

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
@Data
@NoArgsConstructor
//@AllArgsConstructor
@Builder
// 图书类
public class Book implements Serializable {
    private int id; // id
    private String name; // 名称
    private BigDecimal price; // 价格
    private String author; // 作者
    private Integer sales; // 销量
    private Integer stock; // 库存
    private String img_path = "static/img/default.jpg"; // 图书默认地址

    public Book(int id, String name, BigDecimal price, String author, Integer sales, Integer stock, String img_path) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.author = author;
        this.sales = sales;
        this.stock = stock;
        // 判断不为空才赋值
        if (StringUtils.isNotBlank(img_path)) {
            this.img_path = img_path;
        }
    }

这样是有效的。

image-20230927190606459

接下来试下@Builder.Default

image-20230927190611104

怎么用呢?

这个时候,就不能给它值了

1
2
3
4
5
6
@Test
public void addBook() {
    Book book = Book.builder().name("陈志杰自传").author("陈志杰").price(new BigDecimal("100.00")).sales(10000).stock(0).build(); // 不build imgPath
    System.out.println("book = " + book);
    bookDao.addBook(book);
}

插入成功!

@NonNull

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

1
2
3
4
//成员方法参数加上@NonNull注解
public String getName(@NonNull Person p){
return p.getName();
}

实际效果相当于:

1
2
3
4
5
6
public String getName(@NonNull Person p){
if(p==null){
throw new NullPointerException("person");
}
return p.getName();
}

用在构造方法的参数上效果类似,就不再举例子了。

@Cleanup

这个注解用在变量前面,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法,如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)来指定要调用的方法,就用输入输出流来举个例子吧:

1
2
3
4
5
6
7
8
9
10
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);
}
}

实际效果相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}
}
}

是不是简化了很多。

@EqualsAndHashCode

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

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

@Value

@Data注解综合了3,4,5和6里面的@RequiredArgsConstructor注解,其中@RequiredArgsConstructor使用了类中的带有@NonNull注解的或者final修饰的成员变量,它可以使用@Data(staticConstructor=”methodName”)来生成一个静态方法,返回一个调用相应的构造方法产生的对象。这个例子就也省略了吧…

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

@SneakyThrows

这个注解用在方法上,可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用@SneakyThrows(Exception.class)的形式指定抛出哪种异常,很简单的注解,直接看个例子:

1
2
3
4
5
6
7
8
9
10
11
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();
}
}

实际效果相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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和私有final对象lock,当然,也可以自己指定锁对象,例子也很简单,往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");
}
}

实际效果相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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对象时的类名。不同的日志注解总结如下(上面是注解,下面是实际作用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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);

关于lombok的注解先写到这里,当然,还有其他一些注解需要大家自己去摸索,同时lombok一直在扩展,将来肯定会加入更多的注解元素,拭目以待了。

如果你要用slf4j日志:

image-20230927190641740

1
2
3
4
5
6
7
8
<!--log-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
注意导这个包,它会依赖slf4j-api和log4j。这样才是完整的。如果你只导入了slf4j-api,会报错:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder

这样就可以使用@Slf4j了,否则点不出来info的

1
log.info("用户名或密码,登录失败");

@log;@log4j;@slf4j;....等,好几个,要用哪个就导入哪个的jar包,然后加对应的注解即可。

如果要打印info信息,再控制台,或者输出到日志文件,还要配置

log4j.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#log4j.rootLogger 配置的是大于等于当前级别的日志信息的输出
#log4j.rootLogger 用法:(注意appenderName可以是一个或多个)
#log4j.rootLogger = 日志级别,appenderName1,appenderName2,....
#log4j.appender.appenderName1定义的是日志的输出方式,有两种:一种是命令行输出或者叫控制台输出,另一种是文件方式保存
# 1)控制台输出则应该配置为org.apache.log4j.PatternLayout
# 2)文本方式保存应该配置为org.apache.log4j.DailyRollingFileAppender
# 3)也可以自定义 Appender类
#log4j.appender.appenderName1.layout.ConversionPattern 定义的是日志内容格式
#log4j.appender.appenderName1.file 定义了该日志文件的文件名称
#log4j.appender.appenderName1.DatePattern 定义了日志文件重新生成的时间间隔,如果设置到天,则每天重新生成一个新的日志文件。
# 旧的日志文件则以新的文件名保存,文件名称 = log4j.appender.appenderName1.file + log4j.appender.appenderName1.DatePattern
#log4j.appender.appenderName1.Encoding 定义了编码格式
log4j.rootLogger = info,stdout,file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.file=D:/logs/book/info(+).log
log4j.appender.file.DatePattern= '.'yyyy-MM-dd
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n
log4j.appender.file.Encoding=UTF-8

注意,如果配置文件,编码格式后面有空格,会报警告

log4j:WARN Error initializing output writer.

log4j:WARN Unsupported encoding?

如果你报错了:

1
log4j:WARN No appenders could be found for logger (com.nbchen.mybatis.d1.jdbc.JdbcTest).

因为你少加了log4j.properties配置文件

小结

注解

  • @Data

    • 用在类上面的 , 生成set,get, toString, hashCode,canEqual、toString方法
  • @Getter

  • 用在字段, 生成get方法

  • @Setter

    • 用在字段, 生成set方法
  • @ToString

    • 用在类上面的 生成toString方法
  • @xxxConstructor

    • 用在类上面的 生成构造方法 (只能生成无参和全参的构造方法)

优缺点

优点:

  1. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
  2. 让代码变得简洁,不用过多的去关注相应的方法
  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

缺点:

  1. 不支持多种参数构造器的重载