用车
lombok(99%的程序员都在用Lombok,原理竟然这么简单?)
99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

责编 | 郭芮

简介

Lombok 是一个非常热门的开源项目 (https://github.com/rzwitserloot/lombok),使用它可以有效的解决 Java 工程中那些繁琐又重复代码,例如 Setter、Getter、toString、equals、hashCode 以及非空判断等,都可以使用 Lombok 有效的解决。

99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

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; @Singular private Set<String> occupations; public static void main(String[] args) { BuilderExample test = BuilderExample.builder.age(11).name("Java").build; }}

    ⑩ Synchronized 使用

    public class SynchronizedDemo { @Synchronized public 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 类的编译源码发现,代码竟然是这样的:

    99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

    Lombok 的执行流程如下:

    99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

    手撸一个 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; // 提供了创建标识符的方法
    @Override public 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); }
    @Override public 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 { @Override public 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

    源码文件如下:

    99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

    Lombok 优缺点

    缺点1:降低了可调试性

    缺点2:可能会有兼容性问题

    缺点3:可能会坑到队友

    缺点4:破坏了封装性

    也就是说,我们不应该无脑的使用 Lombok 对外暴露所有字段的 Getter/Setter 方法,因为有些字段在某些情况下是不允许直接修改的,比如购物车中的商品数量,它直接影响了购物详情和总价,因此在修改的时候应该提供统一的方法,进行关联修改,而不是给每个字段添加访问和修改的方法。

    99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

    本文我们介绍了 Lombok 的使用以及执行原理,它是通过 JDK 6 实现的 JSR 269: Pluggable Annotation Processing API (编译期的注解处理器) ,在编译期时把 Lombok 的注解转换为 Java 的常规方法的,我们可以通过继承 AbstractProcessor 类,重写它的 init 和 process 方法,实现一个简易版的 Lombok。但同时 Lombok 也存在这一些使用上的缺点,比如:降低了可调试性、可能会有兼容性等问题,因此我们在使用时要根据自己的业务场景和实际情况,来选择要不要使用 Lombok,以及应该如何使用 Lombok。

    声明:本文为作者投稿,版权归其所有。

    99%的程序员都在用Lombok,原理竟然这么简单?nerror="javascript:errorimg.call(this);">

    “Hey Siri” 背后的黑科技大揭秘!

    终端模拟器大 PK,你 Pick 谁?

    程序员造轮子的正确姿势

    编程基础知识真的是九阳神功吗?

    旷视提双边分支网络BBN:攻坚长尾分布的现实世界任务 | CVPR 2020 Oral

    2020年,这20个大家都认识的加密交易所过得怎么样?


    顶一下()     踩一下()

    热门推荐

    发表评论
    0评