lombok简述 引入
org.projectlombok
lombok
provided
1.18.2-RELEASE //版本自定,此为2018年最新版本
|
Lombok注解原理 Lombok这款插件正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。 从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而JDK的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。 需要更加深入理解Lombok插件的细节,自己查阅其源代码是必比可少的。 AnnotationProcessor这个类是Lombok自定义注解处理的入口。该类有两个比较重要的方法一个是init方法,另外一个是process方法。在init方法中,先用来做参数的初始化,将AnnotationProcessor类中定义的内部类(JavacDescriptor、EcjDescriptor)先注册到ProcessorDescriptor类型定义的列表中。其中,内部静态类—JavacDescriptor在其加载的时候就将 lombok.javac.apt.LombokProcessor这个类进行对象实例化并注册。在 LombokProcessor处理器中,其中的process方法会根据优先级来分别运行相应的handler处理类。Lombok中的多个自定义注解都分别有对应的handler处理类。 常见注解简述 构造器注解 @NoArgsConstructor / @RequiredArgsConstrucetor / @AllArgsConstructor 这三个注解都是用在类( @Target(ElementType.TYPE) )上的。 第一个和第三个都很好理解,就是为该类产生无参的构造方法和包含所有参数的构造方法。 第二个注解则使用类中所有带有@NonNull注解的或者带有final修饰的成员变量生成对应的构造方法,当然,和前面几个注解一样,成员变量都是非静态的。另外,如果类中含有final修饰的成员变量,是无法使用@NoArgsConstructor注解的。 三个注解都可以指定生成的构造方法的访问权限,还可指定生成一个静态方法。
//编译前
@AllArgsConstructor
public class Demo {
private String name;
private int age;
}
@AllArgsConstructor
class Parent {
private Integer id;
}
//编译后
public class Demo {
private String name;
private int age;
public Demo(String name, int age) {
this.name = name;
this.age = age;
}
}
//第二个类
class Parent {
private Integer id;
public Parent(Integer id) {
this.id = id;
}
}
|
此注解并不会把父类的属性id拿到Demo的构造器里面去,这是需要注意的地方。并且它也没有默认的构造器了。
//编译前
@AllArgsConstructor(access = AccessLevel.PROTECTED, staticName = "test")
public 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;
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的使用方式同上。 RequiredArgsConstructor看看效果:
//编译前
@RequiredArgsConstructor
public class Demo {
private final int finalVal = 10;
@NonNull
private String name;
@NonNull
private int age;
}
//编译后
public class Demo {
private final int finalVal = 10;
@NonNull
private String name;
@NonNull
private int age;
public Demo(@NonNull String name, @NonNull int age) {
if (name == null) {
throw new NullPointerException("name is marked @NonNull but is null");
} else {
this.name = name;
this.age = age;
}
}
}
|
解释:该注解会识别@nonNull字段,然后以该字段为元素产生一个构造函数。备注:如果所有字段都没有@nonNull注解,那效果同NoArgsConstructor @Builder 提供了一种比较推崇的构建值对象的方式,非常推荐的一种构建值对象的方式。缺点就是父类的属性不能产于builder。
//编译前
@Builder
public 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() {
String var10000 = this.name;
return this.age;
}
}
}
|
builder使用暂不准备示例 build() + builder() @Cleanup 能够自动释放资源,这个注解用在变量前面,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法。 如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)来指定要调用的方法,就用输入输出流来举个例子:
//编译前
public static void main(String[] args) throws Exception {
@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 Exception {
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();
}
}
}
} |
一个简单注解,就实现了优雅的关流操作。 @Data 相当于注解集合。效果等同于@Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor 。 需要注意的是,这里不包括@NoArgsConstructor和@AllArgsConstructor @Value @Value注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。 所以@Value更适合只读性更强的类,所以特殊情况下,还是可以使用的。 推荐优先使用Data @ToString/@EqualsAndHashCode 生成toString,equals和hashcode方法,同时后者还会生成一个canEqual方法,用于判断某个对象是否是当前类的实例。生成方法时只会使用类中的非静态成员变量。 毕竟静态的东西并不属于对象本身。
@ToString
public class Demo {
private final int finalVal = 10;
private transient String name = "aa";
private int age;
}
public static void main(String[] args) throws Exception {
Demo demo = new Demo();
System.out.println(demo);
//Demo(finalVal=10, age=0)
} |
@toString细化使用
@ToString(
includeFieldNames = true, //是否使用字段名
exclude = {"name"}, //排除某些字段
of = {"age"}, //只使用某些字段
callSuper = true //是否让父类字段也参与 默认false
) |
@Getter/@Setter 这一对注解从名字上就很好理解,用在成员变量上面或者类上面,相当于为成员变量生成对应的get和set方法,同时还可以为生成的方法指定访问修饰符,当然,默认为public这两个注解直接用在类上,可以为此类里的所有非静态成员变量生成对应的get和set方法。如果是final变量,那就只会有get方法。
//编译前
@Getter
@Setter
public 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;
public Demo() {
}
public int getFinalVal() {
Objects.requireNonNull(this);
return 10;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
} |
@NotNull 这个注解可以用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常。NPE
//编译前
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();
} |
@SneakyThrows 这个注解用在方法上,可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用@SneakyThrows(Exception.class)的形式指定抛出哪种异常
//Lombok.sneakyThrow源码
public static RuntimeException sneakyThrow(Throwable t) {
if (t == null) {
throw new NullPointerException("t");
} else {
return (RuntimeException)sneakyThrow0(t);
}
}
private static T sneakyThrow0(Throwable t) throws T {
throw t;
}
|
@Synchronized 这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,而@Synchronized得锁对象分别是私有静态final对象LOCK和私有final对象lock,当然,也可以自己指定锁对象。 @Log、CommonsLog、Slf4j、XSlf4j、Log4j、Log4j2等日志注解 这个注解用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不同而不同,同时,可以在注解中使用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);
|
lombok的坑 @Builder和@NoArgsConstructor一起使用冲突问题
//编译报错
Error:(17, 1) java: 无法将类 com.sayabc.groupclass.dtos.appoint.TeaPoolLogicalDelDto中的构造器 TeaPoolLogicalDelDto应用到给定类型;
需要: 没有参数
找到: java.lang.Long,java.lang.Long,java.lang.Long,java.lang.Integer
原因: 实际参数列表和形式参数列表长度不同 |
其实原因很简单,自己点进去看编译后的源码一看便知。 只使用@Builder会自动创建全参构造器。而添加上@NoArgsConstructor后就不会自动产生全参构造器 两种解决方式:
- 去掉@NoArgsConstructor
- 添加@AllArgsConstructor(建议使用这种,毕竟无参构造最好保证是有的)
建议不要加@NoArgsConstructor @builder注解影响设置默认值的问题
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println(demo); //Demo{id=null, age=10}
//采用builder构建 这是我们使用最多的场景吧
Demo demo2 = Demo.builder().build();
System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=null) --- 坑
}
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
private static class Demo {
private Integer id;
private Integer age = 10; //放置默认值年龄
}
|
Warning:(51, 25) java: @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final。 解决方案一:默认字段改为final。 – 一般不符合实际情况 解决方案二:
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
private static class Demo {
private Integer id;
@Builder.Default
private Integer age = 10; //放置默认值年龄
}
|
|