• Java开源工具库使用之Lombok


    前言

    Lombok 是一款在 Java 开发中广受欢迎的工具库,它能够显著简化 Java 代码的编写过程并减少样板代码的冗余。在面对频繁的getter和setter方法、构造函数、日志记录等重复性代码任务时,Lombok 的出现为开发者带来了极大的便利,无需手动编写这些重复性的代码,减少了代码量,提高了开发效率。

    Lombok的使用非常简单,只需在项目中引入 Lombok 库,并在需要的类上添加相应的注解即可。另外,大多数流行的Java集成开发环境(IDE)也都提供了对Lombok 的支持,可以在代码编辑器中正确显示自动生成的代码, IDEA2021 已经内置 Lombok 了。

    文档:https://projectlombok.org/features/

    pom 依赖如下:

    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.20version>
        <scope>providedscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一、常用注解

    1.1 @AllArgsConstructor/@NoArgsConstructor/@RequiredArgsConstructor

    这三个注解能够生成类的构造器

    1. @AllArgsConstructor 能够生成由所有参数构造的构造方法

      @AllArgsConstructor
      public class Test {
      
          private Integer age;
          private String userName;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      生成构造方法,参数顺序为实例类中元素顺序

      public class Test {
          
          private Integer age;
          private String userName;
      
          public Test(Integer age, String userName) {
              this.age = age;
              this.userName = userName;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    2. @NoArgsConstructor 能够生成无参构造方法

      @NoArgsConstructor
      public class Test {
      
          private Integer age = 1;
          private String userName = "";
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      生成无参构造方法

      public class Test {
          
          private Integer age;
          private String userName;
      
          public Test() {
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    3. @RequiredArgsConstructor 可以为类内 final 字段和被 @nonNull 修饰的字段 添加构造方法

      @RequiredArgsConstructor
      public class Test {
      
          private Integer age;
          private String userName;
      
          private final  String password;
      
          @NonNull
          private  String [] lists;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      转化为

      public class Test {
          private Integer age;
          private String userName;
          private final String password;
          private @NonNull String[] lists;
      
          public Test(String password, @NonNull String[] lists) {
              if (lists == null) {
                  throw new NullPointerException("lists is marked non-null but is null");
              } else {
                  this.password = password;
                  this.lists = lists;
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    1.2 @Builder

    @builder 能够生成支持Builder模式的类,提供一种灵活、可读性高且易于维护的方式来构建对象,尤其是当对象具有多个属性,且需要支持可选参数和默认值时,Builder模式特别有用.

    @Builder
    public class Test {
    	@Builder.Default
        private Integer age = 18;
        private String userName;
        private final  String password;
        @NonNull
        private  String [] lists;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    转化如下:

    public class Test {
        private Integer age;
        private String userName;
        private final String password;
        private @NonNull String[] lists;
    
        private static Integer $default$age() {
            return 18;
        }
    
        Test(Integer age, String userName, String password, @NonNull String[] lists) {
            if (lists == null) {
                throw new NullPointerException("lists is marked non-null but is null");
            } else {
                this.age = age;
                this.userName = userName;
                this.password = password;
                this.lists = lists;
            }
        }
    
        public static TestBuilder builder() {
            return new TestBuilder();
        }
    
        public static class TestBuilder {
            private boolean age$set;
            private Integer age$value;
            private String userName;
            private String password;
            private String[] lists;
    
            TestBuilder() {
            }
    
            public TestBuilder age(Integer age) {
                this.age$value = age;
                this.age$set = true;
                return this;
            }
    
            public TestBuilder userName(String userName) {
                this.userName = userName;
                return this;
            }
    
            public TestBuilder password(String password) {
                this.password = password;
                return this;
            }
    
            public TestBuilder lists(@NonNull String[] lists) {
                if (lists == null) {
                    throw new NullPointerException("lists is marked non-null but is null");
                } else {
                    this.lists = lists;
                    return this;
                }
            }
    
            public Test build() {
                Integer age$value = this.age$value;
                if (!this.age$set) {
                    age$value = Test.$default$age();
                }
    
                return new Test(age$value, this.userName, this.password, this.lists);
            }
    
            public String toString() {
                return "Test.TestBuilder(age$value=" + this.age$value + ", userName=" + this.userName + ", password=" + this.password + ", lists=" + Arrays.deepToString(this.lists) + ")";
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    1.3 @Data

    等价于 @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode

    1.4 @EqualsAndHashCode

    能够生成equalshashCode方法, 可通过 callSuper = true 来调用父类的同名方法,不参与计算的属性可通过@EqualsAndHashCode.Exclude进行排除

    @EqualsAndHashCode
    public class Test {
    
        private Integer age = 18;
    
        private String userName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    转化为

    public class Test {
        private Integer age = 18;
        private String userName;
    
        public Test() {
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof Test)) {
                return false;
            } else {
                Test other = (Test)o;
                if (!other.canEqual(this)) {
                    return false;
                } else {
                    Object this$age = this.age;
                    Object other$age = other.age;
                    if (this$age == null) {
                        if (other$age != null) {
                            return false;
                        }
                    } else if (!this$age.equals(other$age)) {
                        return false;
                    }
    
                    Object this$userName = this.userName;
                    Object other$userName = other.userName;
                    if (this$userName == null) {
                        if (other$userName != null) {
                            return false;
                        }
                    } else if (!this$userName.equals(other$userName)) {
                        return false;
                    }
    
                    return true;
                }
            }
        }
    
        protected boolean canEqual(Object other) {
            return other instanceof Test;
        }
    
        public int hashCode() {
            int PRIME = 59; // 这里IDEA反编译有bug,显示为int PRIME = true;
            int result = 1;
            Object $age = this.age;
            result = result * 59 + ($age == null ? 43 : $age.hashCode());
            Object $userName = this.userName;
            result = result * 59 + ($userName == null ? 43 : $userName.hashCode());
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    1.5 @Getter/@Setter

    生成getter和setter方法,默认跳过静态字段和以$开头的字段

    @Getter
    @Setter
    public class Test {
        
        private Integer age = 18;
    
        private String userName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    转化为

    public class Test {
        private Integer age = 18;
        private String userName;
    
        public Test() {
        }
    
        public Integer getAge() {
            return this.age;
        }
    
        public String getUserName() {
            return this.userName;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    1.6 @Slf4j/@Log4j/@Log4j2/@Log

    在类中生成1个字段log,用于记录日志, 使用不同的日志框架可以使用不同的注解

    @log4j2
    public class Test {
        
        private Integer age = 18;
    
        private String userName;
    
    
        public static void main(String[] args) {
            log.info("{}在哪?", "我");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public class Test {
        private static final Logger log = LogManager.getLogger(Test.class);
        private Integer age = 18;
        private String userName;
    
        public Test() {
        }
    
        public static void main(String[] args) {
            log.info("{}在哪?", new Object[]{"我"});
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.7 @ToString

    默认将打印所有非静态字段,可以用@ToString.Exclude注解排除不想打印的字段

    @ToString
    public class Test {
    
        private Integer age;
    
        private String userName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public class Test {
        private Integer age;
        private String userName;
    
        public Test() {
        }
    
        public String toString() {
            return "Test(age=" + this.age + ", userName=" + this.userName + ")";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二、踩坑

    2.1 Getter/Setter 方法名不一样

    在类中,开头只有一个小写字母的字段,如 iPhone, 当使用 Lombok 生成 getter、setter 方法时,它生成getter和setter方法如下:

    public String getIPhone() {
        return this.iPhone;
    }
    public void setIPhone(String iPhone) {
        this.iPhone = iPhone;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    和在IDEA中使用快捷键生成的不一样

    public String getiPhone() {
        return iPhone;
    }
    
    public void setiPhone(String iPhone) {
        this.iPhone = iPhone;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在 SpringBoot 项目中使用 @RequestBody 接收 json 数据时,默认通过 Jackson 处理 ,而 jackson 处理实体,会从getter/setter方法获取具体的字段名,具体源码位于DefaultAccessorNamingStrategy.legacyManglePropertyName, 如下所示:

    /**
     * Method called to figure out name of the property, given 
     * corresponding suggested name based on a method or field name.
     *
     * @param basename Name of accessor/mutator method, not including prefix
     *  ("get"/"is"/"set")
     */
    protected String legacyManglePropertyName(final String basename, final int offset)
    {
        final int end = basename.length();
        if (end == offset) { // empty name, nope
            return null;
        }
        char c = basename.charAt(offset);
        // 12-Oct-2020, tatu: Additional configurability; allow checking that
        //    base name is acceptable (currently just by checking first character)
        if (_baseNameValidator != null) {
            if (!_baseNameValidator.accept(c, basename, offset)) {
                return null;
            }
        }
    
        // next check: is the first character upper case? If not, return as is
        char d = Character.toLowerCase(c);
    
        if (c == d) {
            return basename.substring(offset);
        }
        // otherwise, lower case initial chars. Common case first, just one char
        StringBuilder sb = new StringBuilder(end - offset);
        sb.append(d);
        int i = offset+1;
        for (; i < end; ++i) {
            c = basename.charAt(i);
            d = Character.toLowerCase(c);
            if (c == d) {
                sb.append(basename, i, end);
                break;
            }
            sb.append(d);
        }
        return sb.toString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    以上代码会将生成的 set/get/is 等方法获取字段, 将方法中 set/get/is 按照偏移量移除,然后找到第一个小写的字符,之前的大写字符都会变为小写,这就会导致问题,IPhone会变为 iphone 和字段 iPhone 不同,会导致问题

    Lombok 开发者也意识到这种问题,并提供了解决方案:https://projectlombok.org/features/GetterSetter

    lombok.accessors.capitalization = [basic | beanspec] (default: basic)

    Controls how tricky cases like uShaped (one lowercase letter followed by an upper/titlecase letter) are capitalized. basic capitalizes that to getUShaped, and beanspec capitalizes that to getuShaped instead.
    Both strategies are commonly used in the java ecosystem, though beanspec is more common.

    用 Lombok 的配置来解决。在项目resource目录下创建 lombok.config文件,并添加以下配置项

    lombok.accessors.capitalization = beanspec
    
    • 1

    2.2 @Builder 不会生成无参构造方法

    当使用@Builder后,会有生成全部参数的构造函数,但是没有无参构造方法,这对Spring IOC等框架不太友好,框架需要无参构造函数构造对象。所以,第一感觉就是再加上@NoArgsConstructor,但是又报错了.

    原因分析:如果只是@Builder,那会生成全参构造方法,加上@NoArgsConstructor,全参构造方法就没了。翻看源码文档

    If a class is annotated, then a package-private constructor is generated with all fields as arguments (as if @AllArgsConstructor(access = AccessLevel.PACKAGE) is present on the class), and it is as if this constructor has been annotated with @Builder instead. Note that this constructor is only generated if you haven’t written any constructors and also haven’t added any explicit @XArgsConstructor annotations. In those cases, lombok will assume an all-args constructor is present and generate code that uses it; this means you’d get a compiler error if this constructor is not present

    翻译一下

    如果一个类被注解,那么将生成一个包专用构造函数,其中所有字段都作为参数(就好像类上存在@AllArgsConstructor(access=AccessLevel.package)一样),并且就好像这个构造函数是用@Builder注解的一样。请注意,只有当您没有编写任何构造函数,也没有添加任何显式@XArgsConstructor注解时,才会生成此构造函数。在这些情况下,lombok将假设存在一个all-args构造函数,并生成使用它的代码;这意味着如果这个构造函数不存在,就会出现编译器错误。

    文档说的很明白,当加上@NoArgsConstructor时,不会生成全参构造方法,造成编译错误

    解决方法:很简单,再加上@AllArgsConstructor

    2.3 @Builder 不能build父类属性

    有两种方案:

    1. 添加一个构造方法,包含父类的属性

      @Data
      @AllArgsConstructor
      public class Parent {
          private String foo;
          private Integer bar;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      @ToString(callSuper = true)
      public class Child extends Parent {
      
          private Integer age;
      
          private String userName;
      
          @Builder
          public Child(String foo, Integer bar, Integer age, String userName) {
              super(foo, bar);
              this.age = age;
              this.userName = userName;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    2. 使用@Superbuilder, 这是实验性的 API,不知未来是否删除,慎用

      @Data
      @AllArgsConstructor
      @SuperBuilder
      public class Parent {
          private String foo;
          private Integer bar;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      @ToString(callSuper = true)
      @SuperBuilder
      public class Child extends Parent {
      
          private Integer age;
      
          private String userName;
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    2.4 @ToString 栈溢出

    在使用 JPA 时,实体之间为多对多关系,相互引用,在调用toString方法是陷入无限递归,栈溢出

    可以使用@ToString.Exclude 注解排除多对多引用的字段

    2.5 影响单元测试覆盖率

    在项目中使用了**@Data** 注解,在使用 Jacoco 对代码进行单元测试,会发现测试覆盖率比较低,一些自动生成的方法没有覆盖到

    解决方法:加上以下配置,Lombok会在为由其生成的构造方法、方法、字段和类型中增加@Generated注解,然后Jacoco借助这个注解来实现更为准去的排除。

    config.stopBubbling = true
    lombok.addLombokGeneratedAnnotation = true
    
    • 1
    • 2

    三、源码探秘

    3.1 APT与JSR 269

    编译时注解有以下两种方案:

    1. APT(Annotation Processing Tool),自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

      • api都在com.sun.mirror非标准包下
      • 没有集成到javac中,需要额外运行
    2. JSR-269(Pluggable Annotation Processing API,插件式注解处理器)JDK6 开始纳入了规范,作为apt的替代方案,它解决了apt的以上两个问题。关于处理注解的包在javax.annotation.processing, 集成到javac中,javac 过程如下:

      handle

      • Parse and Enter:所有在命令行中指定的源文件都被读取,解析成语法树,然后所有外部可见的定义都被输入到编译器的符号表中。
      • Annotation Processing:调用所有适当的注解处理器。如果任何注解处理程序生成任何新的源文件或类文件,则重新开始编译,直到没有创建任何新文件为止。
      • Analyse and Generate:最后,解析器创建的语法树将被分析并转换为类文件。在分析过程中,可能会发现对其他类的引用。编译器将检查这些类的源和类路径,如果在源路径上找到它们,也会编译这些文件,尽管它们不需要进行注解处理。

    3.2 实现流程

    在Javac 解析成 AST(Abstract Syntax Tree, 抽象语法树)之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成 JVM 可执行的字节码Class文件。

    具体流程如下:

    1. 在编译Java源代码时,Java编译器会调用注解处理器API。注解处理器会扫描源代码中的注解,找到Lombok相关的注解。
    2. 注解处理器:Lombok的注解处理器会解析并处理这些注解。它会通过解析AST来了解源代码的结构,并根据注解生成相应的代码。
    3. 代码生成:根据注解的类型,Lombok的注解处理器会生成与注解相关的代码片段。例如,@Getter注解会自动生成对应属性的getter方法,@Setter注解会自动生成对应属性的setter方法。
    4. 代码替换:生成的代码片段将会替换原始源代码中与注解相关的部分。这意味着在编译后的字节码中,生成的代码将取代原始代码,从而实现了代码的增强和简化。
    5. 编译结果:最终,通过注解处理器的处理,源代码中标记了Lombok注解的部分将会被替换为生成的代码。这些生成的代码将包含在编译后的类文件中,以便在运行时使用。

    3.3 源码追踪

    打开 lombok.jar 文件,会发现不包含许多.class文件,而是包含名为.SCL.lombok的文件。其实.SCL.lombok文件是.class文件, Lombok 的构建脚本在生成 jar 文件时重命名它们,而 ShadowClassLoader 能够加载这些类,并且首字母缩略词 SCL 似乎来自于此,似乎这样做的原因只是"避免使用基于 SC L的 jar 污染任何项目的命名空间

    lombok jar包从maven下载源码,有部分代码找不到源码,IDEA反编译为空,暂未找到解决方法

    下面以@Get注解为例,查看 lombok 是如何生成getter方法的:

    1. 首先找到的类是LombokProcessor这个类,它继承了AbstractProcessor, 我们知道在自定义一个 APT 的时候需要继承 AbstractProcessor ,并实现其最核心的 process 方法来对当前轮编译的结果进行处理,在 Lombok 中也不例外,Lombok 也是通过一个顶层的 Processor 来接收当前轮的编译结果,而这个 Processor 就是 LombokProcessor 重点关注process方法的这一段

      transformer.transform(prio, javacProcessingEnv.getContext(), cusForThisRound, cleanup);
      
      • 1
    2. JavacTransformer.transform具体如下

      public void transform(long priority, Context context, List<JCCompilationUnit> compilationUnits, CleanupRegistry cleanup) {
          for (JCCompilationUnit unit : compilationUnits) {
              if (!Boolean.TRUE.equals(LombokConfiguration.read(ConfigurationKeys.LOMBOK_DISABLE, JavacAST.getAbsoluteFileLocation(unit)))) {
                  JavacAST ast = new JavacAST(messager, context, unit, cleanup);
                  ast.traverse(new AnnotationVisitor(priority));
                  handlers.callASTVisitors(ast, priority);
                  if (ast.isChanged()) LombokOptions.markChanged(context, (JCCompilationUnit) ast.top().get());
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      获取 AST , traverse 遍历

    3. 继续追踪,找到注解,根据注解位置处理

      public void traverse(JavacASTVisitor visitor) {
          switch (this.getKind()) {
              case COMPILATION_UNIT:
                  visitor.visitCompilationUnit(this, (JCCompilationUnit) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get());
                  break;
              case TYPE:
                  visitor.visitType(this, (JCClassDecl) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitType(this, (JCClassDecl) get());
                  break;
              case FIELD:
                  visitor.visitField(this, (JCVariableDecl) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitField(this, (JCVariableDecl) get());
                  break;
              case METHOD:
                  visitor.visitMethod(this, (JCMethodDecl) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitMethod(this, (JCMethodDecl) get());
                  break;
              case INITIALIZER:
                  visitor.visitInitializer(this, (JCBlock) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitInitializer(this, (JCBlock) get());
                  break;
              case ARGUMENT:
                  JCMethodDecl parentMethod = (JCMethodDecl) up().get();
                  visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod);
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod);
                  break;
              case LOCAL:
                  visitor.visitLocal(this, (JCVariableDecl) get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitLocal(this, (JCVariableDecl) get());
                  break;
              case STATEMENT:
                  visitor.visitStatement(this, get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitStatement(this, get());
                  break;
              case ANNOTATION:
                  switch (up().getKind()) {
                      case TYPE:
                          // @Getter放在类上会执行这段
                          visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get());
                          break;
                      case FIELD:
                          visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get());
                          break;
                      case METHOD:
                          visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get());
                          break;
                      case ARGUMENT:
                          JCVariableDecl argument = (JCVariableDecl) up().get();
                          JCMethodDecl method = (JCMethodDecl) up().up().get();
                          visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get());
                          break;
                      case LOCAL:
                          visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get());
                          break;
                      case TYPE_USE:
                          visitor.visitAnnotationOnTypeUse(up().get(), this, (JCAnnotation) get());
                          break;
                      default:
                          throw new AssertionError("Annotion not expected as child of a " + up().getKind());
                  }
                  break;
              case TYPE_USE:
                  visitor.visitTypeUse(this, get());
                  ast.traverseChildren(visitor, this);
                  visitor.endVisitTypeUse(this, get());
                  break;
              default:
                  throw new AssertionError("Unexpected kind during node traversal: " + getKind());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
    4. 上述 JavacASTVisitor

      public class JavacASTAdapter implements JavacASTVisitor {
          ...
      }
      
      • 1
      • 2
      • 3
      private class AnnotationVisitor extends JavacASTAdapter {
          private final long priority;
      
          AnnotationVisitor(long priority) {
              this.priority = priority;
          }
      
          @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation) {
              JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
              // 执行这段
              handlers.handleAnnotation(top, annotationNode, annotation, priority);
          }
          ...
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    5. 上述handlers时HandlerLibrary类型, HandlerLibrary 中 handleAnnotation如下

      public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) {
      		TypeResolver resolver = new TypeResolver(node.getImportList());
      		String rawType = annotation.annotationType.toString();
      		String fqn = resolver.typeRefToFullyQualifiedName(node, typeLibrary, rawType);
      		if (fqn == null) return;
      		List<AnnotationHandlerContainer<?>> containers = annotationHandlers.get(fqn);
      		if (containers == null) return;
      		
      		for (AnnotationHandlerContainer<?> container : containers) {
      			try {
      				if (container.getPriority() == priority) {
      					if (checkAndSetHandled(annotation)) {
                              // 各个注解handler调用各自的handle方法
      						container.handle(node);
      					} else {
      						if (container.isEvenIfAlreadyHandled()) container.handle(node);
      					}
      				}
      			} catch (AnnotationValueDecodeFail fail) {
      				fail.owner.setError(fail.getMessage(), fail.idx);
      			} catch (Throwable t) {
      				String sourceName = "(unknown).java";
      				if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName();
      				javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t);
      			}
      		}
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
    6. @Get 注解相关handler类为 HandleGetter,重要的handle方法如下:

      public void handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) {
          handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_FLAG_USAGE, "@Getter");
      
          Collection<JavacNode> fields = annotationNode.upFromAnnotationToFields();
          // 将@Getter注解删除
          deleteAnnotationIfNeccessary(annotationNode, Getter.class);
          // 删除lombok 引用包
          deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
          JavacNode node = annotationNode.up();
          Getter annotationInstance = annotation.getInstance();
          AccessLevel level = annotationInstance.value();
          // 判断lazy属性
          boolean lazy = annotationInstance.lazy();
          if (lazy) handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_LAZY_FLAG_USAGE, "@Getter(lazy=true)");
      
          if (level == AccessLevel.NONE) {
              if (lazy) annotationNode.addWarning("'lazy' does not work with AccessLevel.NONE.");
              return;
          }
      
          if (node == null) return;
      
          List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode);
      	// 根据在字段,还是类生成getter方法
          switch (node.getKind()) {
              case FIELD:
                  createGetterForFields(level, fields, annotationNode, true, lazy, onMethod);
                  break;
              case TYPE:
                  if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type.");
                  generateGetterForType(node, annotationNode, level, false, onMethod);
                  break;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34

    四、优缺点

    4.1 优点

    • 最大的优点就是减少样板代码的编写,提高开发效率
    • 通过使用 Lombok,当类的属性发生变化时,不需要手动更新相应的 getter、setter、equals 和 hashCode 方法等,Lombok 会自动帮助生成更新后的代码,提高代码的维护性
    • 大多数主流的 Java IDE(如 IntelliJ IDEA、Eclipse)都对 Lombok 提供了良好的支持,可以正确地识别和处理 Lombok 的注解,帮助开发者在开发过程中更好地理解和调试代码
    • 避免一些工具不支持 Lombok,提供delombok,将被 Lombok 处理后的字节码重新翻译为java源代码

    4.2 缺点

    • 在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果

      举一个简单的例子,我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性

    • 同样的,尽管 Lombok 自动生成的代码可以减少重复性代码,但有时候也可能会导致可读性下降。由于生成的代码被隐藏起来,其他开发人员可能不太容易理解代码的实际逻辑

    • 调试困难:由于Lombok会修改源代码,导致在调试时可能无法准确地查看和追踪生成的代码。这可能会对代码调试和排错造成一定的困扰

    • 版本兼容性:Lombok的注解处理器会直接修改Java源文件,这使得在不同版本的Java编译器和IDE之间使用Lombok可能存在兼容性问题。当你在不同环境中编译或构建项目时,可能需要额外考虑Lombok的版本兼容性

    • 项目编译变慢了

    参考

    1. 这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题
    2. Lombok 原理分析
  • 相关阅读:
    MallBook联合人民交通出版社,推动驾培领域新发展,开启驾培智慧交易新生态
    使用SpringBoot整合数据库连接池Druid的错误总结
    Linux常用操作命令大全
    CANdelaStudio中的状态跳转图无法查看
    houdini 使用HDA Processor 实现处理HDA输入输出
    【译】C# 11 特性的早期预览
    C/C++test——高效完成白盒测试
    关于Redis在windows上运行及fork函数问题
    《计算机网络》期末试卷2020
    使用HTML实现一个静态页面(含源码)
  • 原文地址:https://blog.csdn.net/qq_23091073/article/details/133420421