本文给大家介绍一个代码简化的神器 -- Lombok,主要包含Lombok安装,Lombok的原理、Lombok使用前和使用后代码示例等几方面来讲。
Lombok安装
到Lombok官网,下载Lombok.jar。
点击下载好的lombok.jar, 会出现如下的界面,然后选择Eclipse.exe的路径。
点击"Install / Update" 按钮,然后出现如下弹窗,点击“确定” ,安装完毕。
添加Lombok.jar到工程中去,如果Maven可以添加dependency,如:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
<scope>provided</scope>
</dependency>
然后,随便在工程建一个Book类,如:
import java.util.List;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
public class Book {
private String name;
private List<String> authors;
public void print() {
System.out.println("Hello world");
log.info("info ==> {}", "hello world!!!!");
}
}
Lombok分析
Java源码编译
在弄清Lombok是如何工作的之前,我们先来看一下OpenJDK上对Java源码编译过程的一个说明:
Java 源码编译一般可以分成三个不同的阶段:
- 解析和输入
- 注解处理
- 语义分析和生成class文件
在解析和输入阶段,编译器会解析源文件到一个抽象语法树( Abstract Syntax Tree, 简称AST)。 如果语法不合法将会抛出错误。
在注解处理阶段,自定义的注解处理器将会被调用,这可以被认为是预编译阶段。注解处理器可以做一些譬如验证类正确性、产生新的资源包含源文件等操作。
如果新的源码文件是注解处理的结果,那么编译循环回到解析和输入阶段,重复这个过程,直到没有新的源文件生产为止。
在最后一个阶段,即对抽象语法树(AST) 进行语义分析,编译器根据产生的抽象语法树生成class文件(字节码文件)。
Lombok基本原理
大致了解了Java源码编译的过程之后,我们再来看一下Lombok是如何做的?
Lombok的魔法就在于其修改了AST,分析和生成class阶段使用了修改后的AST,也就最终改变了生成的字节码文件。如,添加一个方法节点 ( Method Node )到AST,那么产生的class文件时将会包含新的方法。
通过修改AST,Lombok可以产生新的方法(如getter、setter等),或者注入代码到已存在的方法中去,比如 ( Lombok 提供的@Cleanup注解 -- 这个可以本文示例中找到 )。
Project Lombok使用了JSR 269 Pluggable Annotation Processing API ,lombok.jar
包含一个名字为 /META-INF/services/javax.annotation.processing.Processor
的文件。当 javac
看到编译路径上的这个文件时,会在编译期间使用定义在这个文件中的注解处理器。
定义的注解处理器主要有两个AnnotationProcessor以及ClaimingProcessor。
AnnotationProcessor以及ClaimingProcessor在Lombok中的源代码如下:
package lombok.launch;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
class AnnotationProcessorHider {
public static class AnnotationProcessor extends AbstractProcessor {
private final AbstractProcessor instance = createWrappedInstance();
@Override public Set<String> getSupportedOptions() {
return instance.getSupportedOptions();
}
@Override public Set<String> getSupportedAnnotationTypes() {
return instance.getSupportedAnnotationTypes();
}
@Override public SourceVersion getSupportedSourceVersion() {
return instance.getSupportedSourceVersion();
}
@Override public void init(ProcessingEnvironment processingEnv) {
instance.init(processingEnv);
super.init(processingEnv);
}
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return instance.process(annotations, roundEnv);
}
@Override public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
return instance.getCompletions(element, annotation, member, userText);
}
private static AbstractProcessor createWrappedInstance() {
ClassLoader cl = Main.createShadowClassLoader();
try {
Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor");
return (AbstractProcessor) mc.newInstance();
} catch (Throwable t) {
if (t instanceof Error) throw (Error) t;
if (t instanceof RuntimeException) throw (RuntimeException) t;
throw new RuntimeException(t);
}
}
}
@SupportedAnnotationTypes("lombok.*")
public static class ClaimingProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return true;
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
}
Project Lombok充当了一个注解处理器的角色。注解处理器扮演了一个分发器,其委托Lombok注解处理器来处理,Lombok注解处理器声明了具体要处理的注解。当委托给一个处理器时,Lombok注解处理器会通过注入新的节点(如,方法、表达式等)的方式去修改抽象语法树 (AST)。在注解处理阶段之后,编译器会根据修改后的AST,生成字节码。
Lombok在源码编译中,大致处理的过程如下图所示:
图来自http://notatube.blogspot.jp/2010/12/project-lombok-creating-custom.html
Lombok实例
接下来,我们就一起来看看使用Lombok能带来什么神奇的效果吧~~ Here we GO~~
@Getter @Setter
为了测试Lombok的@Getter和@Setter注解,先定义一个简单Book类,只包含书名、ISBN以及作者三个属性,并为这个三个属性添加Getter和Setter方法,如:
import java.io.Serializable;
import java.util.List;
/**
*
* @author wangmengjun
*
*/
public class Book implements Serializable {
private static final long serialVersionUID = 7793012904749570902L;
/**书名*/
private String name;
/**ISBN*/
private String isbn;
/**作者*/
private List<String> authors;
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the isbn
*/
public String getIsbn() {
return isbn;
}
/**
* @param isbn the isbn to set
*/
public void setIsbn(String isbn) {
this.isbn = isbn;
}
/**
* @return the authors
*/
public List<String> getAuthors() {
return authors;
}
/**
* @param authors the authors to set
*/
public void setAuthors(List<String> authors) {
this.authors = authors;
}
}
使用Lombok的@Getter和@Setter注解,可以简化Book类,如:
import java.io.Serializable;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
/**
*
* @author wangmengjun
*
*/
@Getter
@Setter
public class Book implements Serializable {
private static final long serialVersionUID = 7793012904749570902L;
/**书名*/
private String name;
/**ISBN*/
private String isbn;
/**作者*/
private List<String> authors;
}
从下图右上角的红框中可以看到,Book类中的三个属性都已经包含了Getter和Setter方法。
构造函数
还是以为上述Book类为例,假设Book类,需要包含两个构造函数,一个是没有参数的,另外一个则包含全部参数,如:
public Book(){
}
/**
* @param name
* @param isbn
* @param authors
*/
public Book(String name, String isbn, List<String> authors) {
super();
this.name = name;
this.isbn = isbn;
this.authors = authors;
}
使用Lombok之后,这样情况只要添加@AllArgsConstructor和@NoArgsConstructor两个注解即可。
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class BookLombok implements Serializable {
private static final long serialVersionUID = 8654008145451673239L;
/**书名*/
private String name;
/**ISBN*/
private String isbn;
/**作者*/
private List<String> authors;
}
如果,Book类需要增加一个出版社press的属性,那么,全部参数的构造函数需要重新添加一个属性,如:
public Book(){
}
/**
* @param name
* @param isbn
* @param authors
* @param press
*/
public Book(String name, String isbn, List<String> authors, String press) {
super();
this.name = name;
this.isbn = isbn;
this.authors = authors;
this.press = press;
}
在这个时候,使用@AllArgsConstructor注解的BookLombok 类将不用再修改任何代码。
@ToString
使@ToString注解,可以为类添加toString方法,如:
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* @author wangmengjun
*
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BookLombok implements Serializable {
private static final long serialVersionUID = 8654008145451673239L;
/**书名*/
private String name;
/**ISBN*/
private String isbn;
/**作者*/
private List<String> authors;
/**出版社*/
private String press;
}
测试一下,
public class Main {
public static void main(String[] args) {
BookLombok book = new BookLombok("软件开发之韵", "ISBN-1-2-3-4", Arrays.asList("Eric", "John"),
"XX出版社");
System.out.println(book);
}
}
运行上述程序,控制台输出如下内容:
BookLombok(name=软件开发之韵, isbn=ISBN-1-2-3-4, authors=[Eric, John], press=XX出版社)
@EqualsAndHashCode
使用@EqualsAndHashCode注解,等价于根据类中定义的变量生成
hashCode
和 equals
方法。
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class BookLombok implements Serializable {
private static final long serialVersionUID = 8654008145451673239L;
/**书名*/
private String name;
/**ISBN*/
private String isbn;
/**作者*/
private List<String> authors;
/**出版社*/
private String press;
}
添加@EqualsAndHashCode注解之后,我们可以在右上角看到BookLombok类已经包含了
hashCode
和 equals
方法。
@Data
使用@Data注解,其效果等价于:
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
- 添加@ToString, @EqualsAndHashCode 和 @RequiredArgsConstructor
- 所有变量增加@Getter
- 所有非final类型的变量增加@Setter
日志记录
日志记录是一个非常重要的东西,来看一下一般我们使用SLF4J来做记录的示例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author wangmengjun
*
*/
public class LogExample {
private static final Logger log = LoggerFactory.getLogger(LogExample.class);
public void greeting(String name) {
System.out.println("Hello, name");
log.debug("Method greeting, name is {}", name);
}
}
测试一下
public class Main {
public static void main(String[] args) {
LogExample example = new LogExample();
example.greeting("WMJ");
}
}
Hello, name
11:32:20.982 [main] DEBUG test.LogExample - Method greeting, name is WMJ
在某个具体类中,如果想要使用日志记录信息,则都需要采用如下的方式,先定义一个Logger对象,如:
private static final Logger log = LoggerFactory.getLogger(LogExample.class);
使用Lombok的日志注解,可以简单代码中对日志对象的定义。
同样使用SLF4J来做,修改后的LogExample类如下:
@Slf4j
public class LogExample {
public void greeting(String name) {
System.out.println("Hello, name");
log.debug("Method greeting, name is {}", name);
}
}
同样,我们测试一下
public class Main {
public static void main(String[] args) {
LogExample example = new LogExample();
example.greeting("WMJ");
}
}
控制台输出如下内容:
Hello, name
11:37:06.828 [main] DEBUG test.LogExample - Method greeting, name is WMJ
相当简单,方便。
其实,Lombok已经为我们提供了多种日志记录的选择,具体有如下几种:
@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);
@Cleanup
使用@CleanUp可以完成自动资源管理,如添加close()方法关闭资源。
参考官方示例如下:
https://projectlombok.org/features/Cleanup.html
等价于
Lombok还提供了很多其它的注解,在本篇文章中就不一一列举出来了。
有兴趣的读者可以访问如下网站获取更多的信息:
https://projectlombok.org/features/index.html
小结
本文主要对Lombok的安装、Lombok原理、使用Lombok的示例等进行了介绍。
Lombok能够简化诸如Getter/Setter , 日志对象创建等代码,使得代码更加的简洁。
因为,Lombok是通过修改抽象语法树,从而代码字节码修改的效果。
那么问题来了 -- 这种方式安全吗?
看看Stackoverflow上,大家怎么说?
个人觉得使用日志注解等,还是挺不错的,后面用Lombok来完成一个小工程先玩玩,看看效果如何。
还不知道国内哪些公司或者项目已经在使用,效果如何?
如大家有相关的信息,也请反馈一下。
【参考】
[2]http://stackoverflow.com/questions/3852091/is-it-safe-to-use-project-lombok?noredirect=1&lq=1
[3]http://notatube.blogspot.jp/2010/11/project-lombok-trick-explained.html
[4]http://notatube.blogspot.jp/2010/12/project-lombok-creating-custom.html
[5]http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html