文章目录
介绍引入Maven依赖常用的注解@NoArgsConstructor/@AllArgsConstructor@RequiredArgsConstructor@Getter/@Setter@ToString/@EqualsAndHashCode@Data@Builder@Accessors 其他注解@SneakyThrows@Value@Cleanup@NotNull@Synchronized@Log、@Log4j、@Slf4j、@Log4j2、@CommonsLog、@XSlf4j等日志注解@Log注解@Log4j注解@Log4j2注解@Slf4j注解 @Delegate@Singular
介绍
官方网址 : https://projectlombok.org/
Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。
在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCode和equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能。
引入Maven依赖
采用最新版本 1.18.24
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> <version>1.18.24</version></dependency>
常用的注解
@NoArgsConstructor/@AllArgsConstructor
@NoArgsConstructor/@AllArgsConstructor就是为该类产生无参的构造方法和包含所有参数的构造方法测试案例1:
@AllArgsConstructorpublic class Demo extends Parent{ private String name; private int age;}@AllArgsConstructor@NoArgsConstructorclass Parent { private Integer id;}
编译后的两个class文件如下:
此注解并不会把父类的属性id拿到Demo的构造器里面去,这是需要注意的地方。
public class Demo { private String name; private int age; public Demo() { } public Demo(String name, int age) { this.name = name; this.age = age; }}class Parent { private Integer id; public Parent(Integer id) { this.id = id; } public Parent() { }}
通过IDEA的类结构, 也可以看出来生成了哪些东西
测试案例2:
通过 @AllArgsConstructor(access = AccessLevel.PROTECTED) , 可以指定构造器的访问权限
@AllArgsConstructor(access = AccessLevel.PROTECTED)public class Demo{ private String name; private int age;}
编译后生成:
public class Demo { private String name; private int age; protected Demo(String name, int age) { this.name = name; this.age = age; }}
测试案例3:
@AllArgsConstructor(access = AccessLevel.PROTECTED, staticName = “test”)
@AllArgsConstructor(access = AccessLevel.PROTECTED, staticName = "test")public class Demo{ private final int finalVal = 10; private String name; private int age;}
编译后 :
public class Demo { private String name; private int age; private Demo(String name, int age) { this.name = name; this.age = age; } protected static Demo test(String name, int age) { return new Demo(name, age); }}
上面效果为:可以指定生成的构造器的访问权限。但是如果指定了一个静态方法,那么构造器会自动会被private,只通过静态方法对外提供访问,并且我们发现final的属性值,是不会放进构造函数里面的。
@NoArgsConstructor的使用方式同@AllArgsConstructor
@RequiredArgsConstructor
@RequiredArgsConstructor注解则会将类中所有带有@NonNull注解 / org.jetbrains.annotations.NotNull注解
的或者带有final修饰的成员变量
生成对应的构造方法
测试案例:
@RequiredArgsConstructorpublic class Demo { @NonNull private final int finalVal; @NonNull private String name; @NonNull private int age;}
编译后生成:
import lombok.NonNull;public class Demo { @NonNull private final int finalVal; @NonNull private String name; @NonNull private int age; public Demo(@NonNull int finalVal, @NonNull String name, @NonNull int age) { if (name == null) { throw new NullPointerException("name is marked non-null but is null"); } else { this.finalVal = finalVal; this.name = name; this.age = age; } }}
解释:该注解会识别@NonNull字段和final修饰得字段,然后以该字段为元素生成一个构造函数。
如果所有字段都没有@NonNull注解,那效果同@NoArgsConstructor ;
@RequiredArgsConstructor 注解的参数, 和其他两个注解一样
注意: 当然这三个生成构造器的注解,要求成员变量都是非静态的, 否则静态变量不会在构造器中被赋值
@Getter/@Setter
这一对注解从名字上就很好理解,用在成员变量上面或者类上面,相当于为成员变量生成对应的get和set方法,同时还可以为生成的方法指定访问修饰符,当然,默认为public
// 如果指定在类上,所有字段都会生成get/set方法// 指定在字段上, 只有标注的字段才会生成get/set方法@Getter@Setterpublic class Demo { private String name; private int age;}
这两个注解直接用在类上,可以为此类里的所有非静态成员变量生成对应的get和set方法。如果是final变量,那就只会有get方法
@ToString/@EqualsAndHashCode
这两个注解也比较好理解,就是生成toString,equals和hashcode方法,同时后者还会生成一个canEqual方法,用于判断某个对象是否是当前类的实例。生成方法时只会使用类中的非静态成员变量。
@ToString@EqualsAndHashCodepublic class Demo { private String name; private int age;}
有些关键的属性,可以控制toString的输出,我们可以了解一下:
//@EqualsAndHashCode也有类似的下面的属性,@ToString( includeFieldNames = true, //是否使用字段名 exclude = {"name"}, //排除某些字段 of = {"age"}, //只使用某些字段 callSuper = true //是否让父类字段也参与 默认false)
@Data
相当于注解集合。效果等同于 @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor 效果同和这5个注解的效果
需要注意的是,这里不包括@NoArgsConstructor和@AllArgsConstructor
所以, 一般使用@Data时,要配合这两个注解一起使用@Datapublic class Demo { private String name; private int age;}
@Builder
@Builder提供了一种比较推崇的构建值对象的方式; 非常推荐的一种构建值对象的方式。
缺点就是父类的属性不能产于builder
标注@Builder的类, 会在类内部生成一个内部类,用于生成值对象
@Builderpublic class Demo { private final int finalVal = 10; private String name; private int age;}
编译后生成:
public class Demo { private final int finalVal = 10; private String name; private int age; Demo(String name, int age) { this.name = name; this.age = age; } public static Demo.DemoBuilder builder() { return new Demo.DemoBuilder(); } public static class DemoBuilder { private String name; private int age; DemoBuilder() { } public Demo.DemoBuilder name(String name) { this.name = name; return this; } public Demo.DemoBuilder age(int age) { this.age = age; return this; } public Demo build() { return new Demo(this.name, this.age); } public String toString() { return "Demo.DemoBuilder(name=" + this.name + ", age=" + this.age + ")"; } }}
使用方式:
public class Main { public static void main(String[] args) { Demo demo = Demo.builder().name("zss").age(20).build(); System.out.println(demo); }}
一般我们给POJO类, 标注的Lombok注解, 百分之90就是这4个 : @Data, @NoArgsConstructor, @AllArgsConstructor, @Builder
@Accessors
@Accessors 一个为getter和setter方法设计的更流畅的注解
这个注解要搭配@Getter与@Setter使用,用来修改默认的setter与getter方法的形式。
@Accessors属性详解
fluent 属性 : 链式的形式 这个特别好用,方法连缀越来越方便了chain 属性 : 流式的形式(若无显示指定chain的值,也会把chain设置为true)prefix 属性 : 生成指定前缀的属性的getter与setter方法,并且生成的getter与setter方法时会去除前缀测试不使用@Accessors时
fluent属性
默认为false,当该值为 true 时,对应字段的 getter 方法前面就没有 get,setter 方法就不会有 set。
编译后生成:
public class Demo { private final int finalVal = 10; private String xxName; private int yyAge; public Demo() { } public int finalVal() { Objects.requireNonNull(this); return 10; } public String xxName() { return this.xxName; } public int yyAge() { return this.yyAge; } public Demo xxName(String xxName) { this.xxName = xxName; return this; } public Demo yyAge(int yyAge) { this.yyAge = yyAge; return this; } public String toString() { int var10000 = this.finalVal(); return "Demo(finalVal=" + var10000 + ", xxName=" + this.xxName() + ", yyAge=" + this.yyAge() + ")"; }}
使用:
public class Main { public static void main(String[] args) { Demo demo = new Demo(); // setter方法; 这里包含了chain=true的功能,可以链式设置值 demo.xxName("lucky").yyAge(20); // getter方法 System.out.println(demo.xxName() + "," + demo.yyAge()); System.out.println("demo = " + demo); }}
chain属性
不写默认为false,当该值为 true 时,对应字段的 setter 方法调用后,会返回当前对象, 进行链式设置值
编译后生成:
public class Demo { private final int finalVal = 10; private String xxName; private int yyAge; public Demo() { } public int getFinalVal() { Objects.requireNonNull(this); return 10; } public String getXxName() { return this.xxName; } public int getYyAge() { return this.yyAge; } public Demo setXxName(String xxName) { this.xxName = xxName; return this; } public Demo setYyAge(int yyAge) { this.yyAge = yyAge; return this; } public String toString() { int var10000 = this.getFinalVal(); return "Demo(finalVal=" + var10000 + ", xxName=" + this.getXxName() + ", yyAge=" + this.getYyAge() + ")"; }}
使用
public class Main { public static void main(String[] args) { Demo demo = new Demo(); // setter方法 demo.setXxName("lucky").setYyAge(20); System.out.println("demo = " + demo); }}
prefix属性
该属性是一个字符串数组,当该数组有值时,表示忽略字段中对应的前缀,生成对应的 getter 和 setter 方法。
如果,我们把它的前缀加到 @Accessors 的属性值中,则可以像没有前缀那样,去调用字段的 getter和 setter 方法。
这里只是把原有属性的前缀给去掉
其他注解
@SneakyThrows
这个注解用在方法上,可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用@SneakyThrows(Exception.class)的形式指定抛出哪种异常
public class Demo { @SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { return new String(bytes, "UTF8"); }}
编译生成后:
public class Demo { public String utf8ToString(byte[] bytes) { try { return new String(bytes, "UTF8"); } catch (UnsupportedEncodingException var3) { throw var3; } }}
@Value
@Value注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。
所以@Value更适合只读性更强的类,所以特殊情况下,还是可以使用的。
@Cleanup
@Cleanup能够自动释放资源; 和 try-with-resources的区别: try-with-resources和lombok的@Cleanup
这个注解用在局部变量
上,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法。
如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)
来指定要调用的方法,就用输入输出流来举个例子吧:
@SneakyThrows(Exception.class)public static void main(String[] args) { @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) { try { FileInputStream in = new FileInputStream(args[0]); try { FileOutputStream out = new FileOutputStream(args[1]); try { byte[] b = new byte[1024]; 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(); } } } catch (Exception var15) { throw var15; }}
@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();}
@Synchronized
作用于方法,可以替换 synchronized 关键字或 lock 锁
这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象
而@Synchronized得锁对象分别是私有静态final对象lock和私有final对象lock,当然,也可以自己指定锁对象
注意: 属性value指定锁对象,当锁对象不存在时,则编译不通过,默认为 “”
public class Demo { private Object obj; @Synchronized public static void hello() { System.out.println("world"); } @Synchronized public int answerToLife() { return 42; } @Synchronized("obj") public void foo() { System.out.println("bar"); }}
编译后生成:
public class Demo { private static final Object $LOCK = new Object[0]; private final Object $lock = new Object[0]; private Object obj; public Demo() { } public static void hello() { synchronized($LOCK) { System.out.println("world"); } } public int answerToLife() { synchronized(this.$lock) { return 42; } } public void foo() { synchronized(this.obj) { System.out.println("bar"); } }}
@Log、@Log4j、@Slf4j、@Log4j2、@CommonsLog、@XSlf4j等日志注解
相对日志进行了解的, 请参考我的博客 : 日志系列
这些注解都有topic属性:设置 getLogger(String name) 方法的参数
这些注解用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录。
具体注解根据日志工具的不同而不同,同时,可以在注解中使用topic来指定生成log对象时的类名。不同的日志注解总结如下(上面是注解,下面是实际作用):
@Log注解
默认是会生成java.util.logging.Logger对象, JUL
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
使用JUL日志框架
@Logpublic class LombokJULTest { /* 日志入口程序 java.util.logging.Logger */ // @Log标注在JULTest类上,就表示自动生成了下面这行代码 // Logger log = Logger.getLogger("com.lucky.jul.JULTest"); @Test public void test01() { // 方式1 log.info("输出info信息1"); // 方式2 log.log(Level.INFO, "输出info信息2"); }}控制台输出:八月 14, 2022 9:17:13 上午 com.lucky.jul.JULTest test01信息: 输出info信息1八月 14, 2022 9:17:13 上午 com.lucky.jul.JULTest test01信息: 输出info信息2
编译后生成:
public class LombokJULTest { private static final Logger log = Logger.getLogger(LombokJULTest.class.getName()); public LombokJULTest() { } @Test public void testLombokLog() { log.info("输出info信息1"); log.log(Level.INFO, "输出info信息2"); }}
@Log4j注解
默认是会生成 org.apache.log4j.Logger 对象
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
使用Log4j框架案例:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency>
log4j配置文件 (resources下, log4j.properties文件)
log4j.rootLogger=trace,console,#配置appender输出方式log4j.appender.console=org.apache.log4j.ConsoleAppender#配置输出的格式log4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.conversionPattern=[%-5p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
@Log4jpublic class LombokLog4jTest { // 使用Lombok的@Log4j注解, 相当于会自动生成下面这个logger对象 // Logger log = Logger.getLogger(LombokLog4jTest.class); @Test public void testLombokLog4j() { // BasicConfigurator.configure(); // 默认指定了log4j的logger,输出位置,输出格式;不需要配置文件也可以打印日志 // debug是默认的级别, 所以不会输出trace信息 log.fatal("fatal信息"); log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}控制台输出:[FATAL]0 com.lucky.log4j.LombokLog4jTestmain2022-08-14 11:02:45:720 fatal信息[ERROR]2 com.lucky.log4j.LombokLog4jTestmain2022-08-14 11:02:45:722 error信息[WARN ]2 com.lucky.log4j.LombokLog4jTestmain2022-08-14 11:02:45:722 warn信息[INFO ]3 com.lucky.log4j.LombokLog4jTestmain2022-08-14 11:02:45:723 info信息
编译后生成代码:
public class LombokLog4jTest { private static final Logger log = Logger.getLogger(LombokLog4jTest.class); public LombokLog4jTest() { } @Test public void testLombokLog4j() { log.fatal("fatal信息"); log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}
@Log4j2注解
前最优秀的Java日志框架, Log4j2也可以充当简单的日志门面来使用
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
使用案例: (本次使用Log4j2充当日志门面,实现简单的日志打印)
<dependencies> <!-- log4j2日志门面 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.12.1</version> </dependency> <!-- log4j2日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.12.1</version> </dependency> <!-- 异步日志依赖 --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.7</version> </dependency></dependencies>
resources目录下, log4j2.xml
<?xml version="1.0" encoding="utf-8" ?><Configuration status="debug"> <!-- 配置appender --> <Appenders> <Console name="consoleAppender" target="SYSTEM_ERR"> <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/> </Console> </Appenders> <!-- 配置logger --> <Loggers> <!-- 配置root logger --> <Root level="trace"> <!-- 引用appender --> <AppenderRef ref="consoleAppender"/> </Root> </Loggers></Configuration>
import lombok.extern.log4j.Log4j2;import org.junit.Test;@Log4j2public class LombokLog4j2Test { // @Log4j2会自动生成下面的Logger对象 // Logger log = LogManager.getLogger(Log4j2Test.class); @Test public void lombokLog4jTest() { log.fatal("fatal信息"); log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}控制台输出:[FATAL] 2022-08-14 11:43:31.845 com.lucky.Log4j2Test lombokLog4jTest 14 main fatal信息[ERROR] 2022-08-14 11:43:31.849 com.lucky.Log4j2Test lombokLog4jTest 15 main error信息[WARN ] 2022-08-14 11:43:31.849 com.lucky.Log4j2Test lombokLog4jTest 16 main warn信息[INFO ] 2022-08-14 11:43:31.849 com.lucky.Log4j2Test lombokLog4jTest 17 main info信息[DEBUG] 2022-08-14 11:43:31.850 com.lucky.Log4j2Test lombokLog4jTest 18 main debug信息[TRACE] 2022-08-14 11:43:31.850 com.lucky.Log4j2Test lombokLog4jTest 19 main trace信息
编译后生成:
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.junit.Test;public class LombokLog4j2Test { private static final Logger log = LogManager.getLogger(LombokLog4j2Test.class); public LombokLog4j2Test() { } @Test public void lombokLog4jTest() { log.fatal("fatal信息"); log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}
@Slf4j注解
Slf4j日志门面(Simple Logging Facade For Java) , Slf4j主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
使用案例: (Slf4j + Log4j2实现日志输出)
<!-- slf4j日志门面 --><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version></dependency><!-- log4j2适配器 --><dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.12.1</version></dependency><!-- log4j2日志门面 --><dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.12.1</version></dependency><!-- log4j2日志实现 --><dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.12.1</version></dependency><!-- 异步日志依赖 --><dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.7</version></dependency>
resources目录下, log4j2.xml
<?xml version="1.0" encoding="utf-8" ?><Configuration status="debug"> <!-- 配置appender --> <Appenders> <Console name="consoleAppender" target="SYSTEM_ERR"> <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/> </Console> </Appenders> <!-- 配置logger --> <Loggers> <!-- 配置root logger --> <Root level="trace"> <!-- 引用appender --> <AppenderRef ref="consoleAppender"/> </Root> </Loggers></Configuration>
import lombok.extern.slf4j.Slf4j;import org.junit.Test;@Slf4jpublic class LombokSlf4jTest { // @Slf4j会自动生成下面这个Logger对象 // Logger log = LoggerFactory.getLogger(LombokSlf4jTest.class); @Test public void lombokSlf4jTest() { log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}
编译后生成:
import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class LombokSlf4jTest { private static final Logger log = LoggerFactory.getLogger(LombokSlf4jTest.class); public LombokSlf4jTest() { } @Test public void lombokSlf4jTest() { log.error("error信息"); log.warn("warn信息"); log.info("info信息"); log.debug("debug信息"); log.trace("trace信息"); }}
其他的Lombok日志注解, 自行了解咯
@CommonsLogprivate static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);@JBossLogprivate static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);@XSlf4jprivate static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@Delegate
被@Delegate注释的属性,会把这个属性类型的公有非静态方法合到当前类
public class Person { public void personMsg() { System.out.println("Person.personMsg"); } public String printName(String name) { return name; } private Integer printAge(Integer age) { return age; } public static void printOther() { System.out.println("Person.printOther"); }}
@Getter@Setterpublic class Demo { @Delegate private Person person;}
编译后生成:
public class Demo { private Person person; public Demo() { } public Person getPerson() { return this.person; } public void setPerson(Person person) { this.person = person; } public void personMsg() { this.getPerson().personMsg(); } public String printName(String name) { return this.getPerson().printName(name); }}
使用:
public class Main { public static void main(String[] args) { Demo demo = new Demo(); demo.setPerson(new Person()); demo.personMsg(); System.out.println(demo.printName("lucky")); }}
@Singular
使用 @Singular 注解一个集合字段(如果没有指定 value 属性值,那么集合字段名需要是复数形式),会生成添加元素方法向集合添加单个元素
只能配合@Builder注解使用, 该注解作用于字段和参数上, 一般用在集合属性和集合参数
使用:
public class Main { public static void main(String[] args) { Demo demo = Demo.builder().name("lucky") .num(1).num(2).num(3) .build(); System.out.println("demo = " + demo); }}控制台输出:demo = Demo(name=lucky, nums=[1, 2, 3])
lombok: 优秀blog