背景
我们在开发过程中,通常都会定义大量的JavaBean,然后通过IDE去生成其属性的构造器、getter、setter、equals、hashcode、toString方法,当要对某个属性进行改变时,比如命名、类型等,都需要重新去生成上面提到的这些方法,那Java中有没有一种方式能够避免这种重复的劳动呢?答案是有,我们来看一下下面这张图,右面是一个简单的JavaBean,只定义了两个属性,在类上加上了@Data
,从左面的结构图上可以看到,已经自动生成了上面提到的方法。
Lombok简介
Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。官方地址:https://projectlombok.org/,github地址:https://github.com/rzwitserloot/lombok。
Lombok使用环境
# | # |
---|---|
Maven, Ivy or Gradle | Lombok is in maven central. More… |
Javac | Just put lombok.jar on the classpath. |
NetBeans | Just put lombok.jar on the classpath and enable annotation processing. More… |
Eclipse and variants | Run lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project. Supported variants: Springsource Tool Suite, JBoss Developer Studio |
IDEA IntelliJ | A plugin developed by Michael Plushnikov adds support for most features. |
Javadoc | First delombok your code then run javadoc on the result. More… |
Android | The proper way to use lombok with android is somewhat complicated but possible. More… |
GWT | Lombok works with GWT. More… |
Play! Framework | Use Aaron Freeman’s lombok play plugin. |
ecj | Lombok works on ecj and ecj-based tools. More… |
IntelliJ IDEA 使用它的方法
- 先安装插件
![](http://upload-images.jianshu.io/upload_images/4259109-ab6cf3975cc8e533.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/447)
- 然后引入lombok的jar包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
</dependency>
注解的介绍
@Getter and @Setter
你可以用@Getter / @Setter注释任何字段(当然也可以注释到类上的),让lombok自动生成默认的getter / setter方法。
默认生成的方法是public的,如果要修改方法修饰符可以设置AccessLevel的值,例如:@Getter(access = AccessLevel.PROTECTED)
![](http://upload-images.jianshu.io/upload_images/4259109-a6ddbbc89d099cee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/645)
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class User {
@Getter(AccessLevel.PROTECTED) @Setter private Integer id;
@Getter @Setter private String name;
@Getter @Setter private String phone;
}
@ToString
生成toString()方法,默认情况下,它会按顺序(以逗号分隔)打印你的类名称以及每个字段。可以这样设置不包含哪些字段@ToString(exclude = "id") / @ToString(exclude = {"id","name"})
如果继承的有父类的话,可以设置callSuper 让其调用父类的toString()方法,例如:@ToString(callSuper = true)
import lombok.ToString;
@ToString(exclude = {"id","name"})
public class User {
private Integer id;
private String name;
private String phone;
}
生成toString方法如下:
public String toString(){
return "User(phone=" + phone + ")";
}
@EqualsAndHashCode
生成hashCode()和equals()方法,默认情况下,它将使用所有非静态,非transient字段。但可以通过在可选的exclude参数中来排除更多字段。或者,通过在parameter参数中命名它们来准确指定希望使用哪些字段。
@EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private transient int id;
public String getName() {
return this.name;
}
@EqualsAndHashCode(callSuper=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
对比代码如下:
import java.util.Arrays;
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private transient int id;
public String getName() {
return this.name;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof EqualsAndHashCodeExample)) return false;
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (Double.compare(this.score, other.score) != 0) return false;
if (!Arrays.deepEquals(this.tags, other.tags)) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.score);
result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.tags);
return result;
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
if (!other.canEqual((Object)this)) return false;
if (!super.equals(o)) return false;
if (this.width != other.width) return false;
if (this.height != other.height) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + super.hashCode();
result = (result*PRIME) + this.width;
result = (result*PRIME) + this.height;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Square;
}
}
}
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
@NoArgsConstructor生成一个无参构造方法。当类中有final字段没有被初始化时,编译器会报错,此时可用@NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0 / false / null。对于具有约束的字段(例如@NonNull字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。
import lombok.NoArgsConstructor;
import lombok.NonNull;
@NoArgsConstructor(force = true)
public class User {
@NonNull private Integer id;
@NonNull private String name;
private final String phone ;
}
@RequiredArgsConstructor会生成构造方法(可能带参数也可能不带参数),如果带参数,这参数只能是以final修饰的未经初始化的字段,或者是以@NonNull注解的未经初始化的字段
@RequiredArgsConstructor(staticName = "of")会生成一个of()的静态方法,并把构造方法设置为私有的
![](http://upload-images.jianshu.io/upload_images/4259109-b9b1cd39fd30695e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/631)
![](http://upload-images.jianshu.io/upload_images/4259109-cb5965d0ab3c66cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/646)
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class User {
@NonNull private Integer id ;
@NonNull private String name = "bbbb";
private final String phone;
}
//另外一个
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(staticName = "of")
public class User {
@NonNull private Integer id ;
@NonNull private String name = "bbbb";
private final String phone;
}
@AllArgsConstructor 生成一个全参数的构造方法
![](http://upload-images.jianshu.io/upload_images/4259109-df8e5461d650eb85.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700)
import lombok.AllArgsConstructor;
import lombok.NonNull;
@AllArgsConstructor
public class User {
@NonNull private Integer id ;
@NonNull private String name = "bbbb";
private final String phone;
}
@Data
@Data 包含了 @ToString、@EqualsAndHashCode、@Getter / @Setter和@RequiredArgsConstructor的功能
@Accessors
@Accessors 主要用于控制生成的getter和setter
主要参数介绍- fluent boolean值,默认为false。此字段主要为控制生成的getter和setter方法前面是否带get/set
- chain boolean值,默认false。如果设置为true,setter返回的是此对象,方便链式调用方法
- prefix 设置前缀 例如:@Accessors(prefix = "abc") private String abcAge 当生成get/set方法时,会把此前缀去掉
Paste_Image.png@Synchronized
给方法加上同步锁
import lombok.Synchronized;
public class SynchronizedExample {
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 SynchronizedExample {
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");
}
}
}
@Wither
提供了给final字段赋值的一种方法
//使用lombok注解的
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.experimental.Wither;
public class WitherExample {
@Wither private final int age;
@Wither(AccessLevel.PROTECTED) @NonNull private final String name;
public WitherExample(String name, int age) {
if (name == null) throw new NullPointerException();
this.name = name;
this.age = age;
}
}
//等效代码
import lombok.NonNull;
public class WitherExample {
private final int age;
private @NonNull final String name;
public WitherExample(String name, int age) {
if (name == null) throw new NullPointerException();
this.name = name;
this.age = age;
}
public WitherExample withAge(int age) {
return this.age == age ? this : new WitherExample(age, name);
}
protected WitherExample withName(@NonNull String name) {
if (name == null) throw new java.lang.NullPointerException("name");
return this.name == name ? this : new WitherExample(age, name);
}
}
@onX
在注解里面添加注解的方式
直接看代码
public class SchoolDownloadLimit implements Serializable {
private static final long serialVersionUID = -196412797757026250L;
@Getter(onMethod = @_({@Id,@Column(name="id",nullable=false),@GeneratedValue(strategy= GenerationType.AUTO)}))
@Setter
private Integer id;
@Getter(onMethod = @_(@Column(name="school_id")))
@Setter
private Integer schoolId;
@Getter(onMethod = @_(@Column(name = "per_download_times")))
@Setter
private Integer perDownloadTimes;
@Getter(onMethod = @_(@Column(name = "limit_time")))
@Setter
private Integer limitTime;
@Getter(onMethod = @_(@Column(name = "download_to_limit_an_hour")))
@Setter
private Integer downloadToLimitInHour;
@Getter(onMethod = @_(@Column(name = "available")))
@Setter
private Integer available = 1;
}
@Builder
@Builder注释为你的类生成复杂的构建器API。
lets you automatically produce the code required to have your class be instantiable with code such as:
Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
直接看官方示例,对比一下就都明白了
//使用lombok注解的
import lombok.Builder;
import lombok.Singular;
import java.util.Set;
@Builder
public class BuilderExample {
private String name;
private int age;
@Singular private Set<String> occupations;
}
//等效代码
import java.util.Set;
class BuilderExample {
private String name;
private int age;
private Set<String> occupations;
BuilderExample(String name, int age, Set<String> occupations) {
this.name = name;
this.age = age;
this.occupations = occupations;
}
public static BuilderExampleBuilder builder() {
return new BuilderExampleBuilder();
}
public static class BuilderExampleBuilder {
private String name;
private int age;
private java.util.ArrayList<String> occupations;
BuilderExampleBuilder() {
}
public BuilderExampleBuilder name(String name) {
this.name = name;
return this;
}
public BuilderExampleBuilder age(int age) {
this.age = age;
return this;
}
public BuilderExampleBuilder occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.addAll(occupations);
return this;
}
public BuilderExampleBuilder clearOccupations() {
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public BuilderExample build() {
// complicated switch statement to produce a compact properly sized immutable set omitted.
// go to https://projectlombok.org/features/Singular-snippet.html to see it.
Set<String> occupations = ...;
return new BuilderExample(name, age, occupations);
}
@java.lang.Override
public String toString() {
return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
}
}
}
@Delegate
这个注解也是相当的牛逼,看下面的截图,它会该类生成一些列的方法,这些方法都来自与List接口
Paste_Image.png
附带一个我使用的例子
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name= Constants.TABLE_SCHOOL_DOWNLOAD_LIMIT)
@RequiredArgsConstructor(staticName = "of")
@Accessors(chain = true)
@ToString
public class SchoolDownloadLimit implements Serializable {
private static final long serialVersionUID = -196412797757026250L;
@Getter(onMethod = @_({@Id,@Column(name="id",nullable=false),@GeneratedValue(strategy= GenerationType.AUTO)}))
@Setter
private Integer id;
@Getter(onMethod = @_(@Column(name="school_id")))
@Setter
private Integer schoolId;
@Getter(onMethod = @_(@Column(name = "per_download_times")))
@Setter
private Integer perDownloadTimes;
@Getter(onMethod = @_(@Column(name = "limit_time")))
@Setter
private Integer limitTime;
@Getter(onMethod = @_(@Column(name = "download_to_limit_an_hour")))
@Setter
private Integer downloadToLimitInHour;
@Getter(onMethod = @_(@Column(name = "available")))
@Setter
private Integer available = 1;
@Getter(onMethod = @_(@Column(name = "create_time")))
@Setter
private Date createTime;
@Getter(onMethod = @_(@Column(name = "update_time")))
@Setter
private Date updateTime;
}
.@val @var
使用Lombok ,java也能够像javascript一样使用弱类型定义变量了
val注解变量申明是final类型 var注解变量是非final类型
- import java.util.ArrayList;
- import java.util.HashMap;
- import lombok.val;
- public class ValExample {
- public String example() {
- val example = new ArrayList<String>();
- example.add("Hello, World!");
- val foo = example.get(0);
- return foo.toLowerCase();
- }
- public void example2() {
- val map = new HashMap<Integer, String>();
- map.put(0, "zero");
- map.put(5, "five");
- for (val entry : map.entrySet()) {
- System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
- }
- }
- }
- <span style="font-weight:normal;">import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Map;
- public class ValExample {
- public String example() {
- final ArrayList<String> example = new ArrayList<String>();
- example.add("Hello, World!");
- final String foo = example.get(0);
- return foo.toLowerCase();
- }
- public void example2() {
- final HashMap<Integer, String> map = new HashMap<Integer, String>();
- map.put(0, "zero");
- map.put(5, "five");
- for (final Map.Entry<Integer, String> entry : map.entrySet()) {
- System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
- }
- }
- }</span>
使用该注解能够自动释放io资源
- <span style="font-weight:normal;"> import lombok.Cleanup;
- import java.io.*;
- public class CleanupExample {
- 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[10000];
- while (true) {
- int r = in.read(b);
- if (r == -1) break;
- out.write(b, 0, r);
- }
- }
- }</span>
- <span style="font-weight:normal;">import java.io.*;
- public class CleanupExample {
- 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();
- }
- }
- }
- }</span>
当然从1.7开始jdk已经提供了try with resources的方式自动回收资源
- static String readFirstLineFromFile(String path) throws IOException {
- try (BufferedReader br = new BufferedReader(new FileReader(path))) {
- return br.readLine();
- }
- }
@Value
@value是@data的不可变对象 (不可变对象的用处和创建:https://my.oschina.net/jasonultimate/blog/166810)
所有字段都是私有的,默认情况下是final的,并且不会生成setter。默认情况下,类本身也是final的,因为不可变性不能强制转化为子类。与@data一样,有用toString()、equals()和hashCode()方法也是生成的,每个字段都有一个getter方法,并且一个覆盖每个参数的构造器也会生成。
把checked异常转化为unchecked异常,好处是不用再往上层方法抛出了,美其名曰暗埋异常
- import lombok.SneakyThrows;
- public class SneakyThrowsExample implements Runnable {
- @SneakyThrows(UnsupportedEncodingException.class)
- public String utf8ToString(byte[] bytes) {
- return new String(bytes, "UTF-8");
- }
- @SneakyThrows
- public void run() {
- throw new Throwable();
- }
- }
- import lombok.Lombok;
- public class SneakyThrowsExample implements Runnable {
- public String utf8ToString(byte[] bytes) {
- try {
- return new String(bytes, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw Lombok.sneakyThrow(e);
- }
- }
- public void run() {
- try {
- throw new Throwable();
- } catch (Throwable t) {
- throw Lombok.sneakyThrow(t);
- }
- }
- }
@Log
可以生成各种log对象,方便多了
- import lombok.extern.java.Log;
- import lombok.extern.slf4j.Slf4j;
- @Log
- public class LogExample {
- public static void main(String... args) {
- log.error("Something's wrong here");
- }
- }
- @Slf4j
- public class LogExampleOther {
- public static void main(String... args) {
- log.error("Something else is wrong here");
- }
- }
- @CommonsLog(topic="CounterLog")
- public class LogExampleCategory {
- public static void main(String... args) {
- log.error("Calling the 'CounterLog' with a message");
- }
- }
- public class LogExample {
- private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
- public static void main(String... args) {
- log.error("Something's wrong here");
- }
- }
- public class LogExampleOther {
- private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
- public static void main(String... args) {
- log.error("Something else is wrong here");
- }
- }
- public class LogExampleCategory {
- private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog("CounterLog");
- public static void main(String... args) {
- log.error("Calling the 'CounterLog' with a message");
- }
- }
所有支持的log类型:
@CommonsLog
Creates
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@JBossLog
Creates
private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Log
Creates
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
Creates
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
Creates
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
Creates
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
Creates
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
扩展配置:
lombok.log.fieldName
=
an identifier
(default:
log
).生成log字段的名称 默认为log
lombok.log.fieldIsStatic
= [
true
|
false
] (default: true)生成log是否是static的 默认为static
Lombok原理
了解了简单的使用之后,现在应该比较好奇它是如何实现的。整个使用的过程中,只需要使用注解而已,不需要做其它额外的工作,那玄妙之处应该是在注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。
运行时解析
运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样可以通过反射拿到该注解。java.lang.reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,大部分开发者应该都很熟悉这种解析方式。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();
- 1
- 2
- 3
- 4
编译时解析
编译时解析有两种机制,网上很多文章都把它俩搞混了,分别简单描述一下。
Annotation Processing Tool
apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:
- api都在com.sun.mirror非标准包下
- 没有集成到javac中,需要额外运行
apt的更多介绍可以参见这里。
Pluggable Annotation Processing API
JSR 269,自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:
Lombok就是使用这种方式实现的,有兴趣的话可以去看看其Lombok源码,对应注解的实现都在HandleXXX中,比如@Getter注解的实现是HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google Auto、Dagger等等。
Lombok问题
- 无法支持多种参数构造器的重载
- 奇淫巧技,使用会有争议
ide中使用Lombok的注意事项
1.项目中要使用lombok 不仅ide要支持(否则一堆错误),项目中也要引入jar包
2.如果配置lombok.config文件,修改文件的属性值后,并不会自动重新编译class文件,ide编辑器也不会自动更新,所有每次修改配置文件后最后关闭java文件窗口重新打开,并且clean下项目