责编 | 郭芮
简介
Lombok 是一个非常热门的开源项目 (https://github.com/rzwitserloot/lombok),使用它可以有效的解决 Java 工程中那些繁琐又重复代码,例如 Setter、Getter、toString、equals、hashCode 以及非空判断等,都可以使用 Lombok 有效的解决。
1.添加 Lombok 插件
点击 File > Settings > Plugins 进入插件管理页面
搜索 Lombok Plugin
重启 IntelliJ IDEA
2.添加 Lombok 库
如果是 JDK 9+ 可使用模块的方式添加,配置如下:
<annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></path></annotationProcessorPaths>
接下来到了前半部分中最重要的 Lombok 使用环节了,我们先来看在没有使用 Lombok 之前的代码:
public class Person {private Integer id;private String name;public Integer getId {return id;}public void setId(Integer id) {this.id = id;}public String getName {return name;}public void setName(String name) {this.name = name;}}
可以看出在 Lombok 之后,用一个注解就搞定了之前所有 Getter/Setter 的代码,让代码瞬间优雅了很多。
val:用在局部变量前面,相当于将变量声明为 final;
@Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成 try-finally 这样的代码来关闭流;
@ToString:用在类上可以自动覆写 toString 方法,当然还可以加其他参数,例如 @ToString(exclude=”id”) 排除 id 属性,或者 @ToString(callSuper=true, includeFieldNames=true) 调用父类的 toString 方法,包含所有属性;
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有 @Non 属性作为参数的构造函数,如果指定 staticName="of" 参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多;
@Value:用在类上,是 @Data 的不可变形式,相当于为属性添加 final 声明,只提供 getter 方法,而不提供 setter 方法;
@SneakyThrows:自动抛受检异常,而无需显式在方法上使用 throws 语句;
@Getter(lazy=true):可以替代经典的 Double Check Lock 样板代码;
@CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
它们的具体使用如下:
② Non 使用
public void notExample(@Non String string) {string.length;}// 相当于public void notExample(String string) {if (string != ) {string.length;} else {throw new PointerException("");}}
④ Getter/Setter 使用
@Setter(AccessLevel.PUBLIC)@Getter(AccessLevel.PROTECTED)private int id;private String shap;
⑥ EqualsAndHashCode 使用
@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false)public class LombokDemo {private int id;private String shap;}
⑧ Builder 使用
@Builderpublic class BuilderExample {private String name;private int age;@Singularprivate Set<String> occupations;public static void main(String[] args) {BuilderExample test = BuilderExample.builder.age(11).name("Java").build;}}
⑩ Synchronized 使用
public class SynchronizedDemo {@Synchronizedpublic static void hello {System.out.println("world");}// 相当于private static final Object $LOCK = new Object[0];public static void hello {synchronized ($LOCK) {System.out.println("world");}}}
原理分析
解析与填充符号表
分析与字节码生成
而 Lombok 正是利用「注解处理」这一步进行实现的,Lombok 使用的是 JDK 6 实现的 JSR 269: Pluggable Annotation Processing API (编译期的注解处理器) ,它是在编译期时把 Lombok 的注解代码,转换为常规的 Java 方法而实现优雅地编程的。
在我们编译之后,查看 Person 类的编译源码发现,代码竟然是这样的:
Lombok 的执行流程如下:
手撸一个 Lombok
自定义一个注解标签接口,并实现一个自定义的注解处理器;
使用自定义的注解处理器编译代码。
1.定义自定义注解和注解处理器
再实现一个自定义的注解处理器,代码如下:
import com.sun.source.tree.Tree;import com.sun.tools.javac.api.JavacTrees;import com.sun.tools.javac.code.Flags;import com.sun.tools.javac.code.Type;import com.sun.tools.javac.processing.JavacProcessingEnvironment;import com.sun.tools.javac.tree.JCTree;import com.sun.tools.javac.tree.TreeMaker;import com.sun.tools.javac.tree.TreeTranslator;import com.sun.tools.javac.util.*;import javax.annotation.processing.*;import javax.lang.model.SourceVersion;import javax.lang.model.element.Element;import javax.lang.model.element.TypeElement;import javax.tools.Diagnostic;import java.util.Set;@SupportedSourceVersion(SourceVersion.RELEASE_8)@SupportedAnnotationTypes("com.example.lombok.MyGetter")public class MyGetterProcessor extends AbstractProcessor {private Messager messager; // 编译时期输入日志的private JavacTrees javacTrees; // 提供了待处理的抽象语法树private TreeMaker treeMaker; // 封装了创建AST节点的一些方法private Names names; // 提供了创建标识符的方法@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);this.messager = processingEnv.getMessager;this.javacTrees = JavacTrees.instance(processingEnv);Context context = ((JavacProcessingEnvironment) processingEnv).getContext;this.treeMaker = TreeMaker.instance(context);this.names = Names.instance(context);}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyGetter.class);elementsAnnotatedWith.forEach(e -> {JCTree tree = javacTrees.getTree(e);tree.accept(new TreeTranslator {@Overridepublic void visitClassDef(JCTree.JCClassDecl jcClassDecl) {List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil;// 在抽象树中找出所有的变量for (JCTree jcTree : jcClassDecl.defs) {if (jcTree.getKind.equals(Tree.Kind.VARIABLE)) {JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);}}// 对于变量进行生成方法的操作jcVariableDeclList.forEach(jcVariableDecl -> {messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName + " has been processed");jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));});super.visitClassDef(jcClassDecl);}});});return true;}private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>;// 生成表达式 例如 this.a = a;JCTree.JCexpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName), treeMaker.Ident(jcVariableDecl.getName));statements.append(aThis);JCTree.JCBlock block = treeMaker.Block(0, statements.toList);// 生成入参JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),jcVariableDecl.getName, jcVariableDecl.vartype, );List<JCTree.JCVariableDecl> parameters = List.of(param);// 生成返回对象JCTree.JCexpression methodType = treeMaker.Type(new Type.JCVoidType);return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),getNewMethodName(jcVariableDecl.getName), methodType, List.nil,parameters, List.nil, block, );}private Name getNewMethodName(Name name) {String s = name.toString;return names.fromString("get" + s.substring(0, 1).toUpperCase + s.substring(1, name.length));}private JCTree.JCexpressionStatement makeAssignment(JCTree.JCexpression lhs, JCTree.JCexpression rhs) {return treeMaker.Exec(treeMaker.Assign(lhs,rhs));}}
当这些代码写好之后,我们就可以新增一个 Person 类来试一下我们自定义的 @MyGetter 功能了,代码如下:
@MyGetterpublic class Person {private String name;}
上面的所有流程执行完成之后,我们就可以编译代码测试效果了。首先,我们先进入代码的根目录,执行以下三条命令。
① 使用 tools.jar 编译自定义的注解器
javac -cp $JAVA_HOME/lib/tools.jar MyGetter* -d .② 使用自定义注解器,编译 Person 类
javac -processor com.example.lombok.MyGetterProcessor Person.java源码文件如下:
Lombok 优缺点
缺点1:降低了可调试性
缺点2:可能会有兼容性问题
缺点3:可能会坑到队友
缺点4:破坏了封装性
也就是说,我们不应该无脑的使用 Lombok 对外暴露所有字段的 Getter/Setter 方法,因为有些字段在某些情况下是不允许直接修改的,比如购物车中的商品数量,它直接影响了购物详情和总价,因此在修改的时候应该提供统一的方法,进行关联修改,而不是给每个字段添加访问和修改的方法。
本文我们介绍了 Lombok 的使用以及执行原理,它是通过 JDK 6 实现的 JSR 269: Pluggable Annotation Processing API (编译期的注解处理器) ,在编译期时把 Lombok 的注解转换为 Java 的常规方法的,我们可以通过继承 AbstractProcessor 类,重写它的 init 和 process 方法,实现一个简易版的 Lombok。但同时 Lombok 也存在这一些使用上的缺点,比如:降低了可调试性、可能会有兼容性等问题,因此我们在使用时要根据自己的业务场景和实际情况,来选择要不要使用 Lombok,以及应该如何使用 Lombok。
声明:本文为作者投稿,版权归其所有。
“Hey Siri” 背后的黑科技大揭秘!
终端模拟器大 PK,你 Pick 谁?
程序员造轮子的正确姿势
编程基础知识真的是九阳神功吗?
旷视提双边分支网络BBN:攻坚长尾分布的现实世界任务 | CVPR 2020 Oral
