1. Lombok基本使用

  1. maven引入依赖

    1. <dependency>
    2. <groupId>org.projectlombok</groupId>
    3. <artifactId>lombok</artifactId>
    4. <version>1.16.14</version>
    5. </dependency>
    6. @Getter
    7. @Setter
    8. @AllArgsConstructor
    9. @NoArgsConstructor
    10. public class User {
    11. private String name;
    12. }
  2. 自动生成get,set方法,全参构造器,无参构造器

    1. public class Client {
    2. public static void main(String[] args) {
    3. User user = new User();
    4. user.setName("lisi");
    5. System.out.println(user.getName());
    6. }
    7. }

2. Lombok原理

我们知道 Java 的编译过程大致可以分为三个阶段:

  1. 解析与填充符号表
  2. 注解处理
  3. 分析与字节码生成

编译过程:
Lombok 原理及简单实现 - 图1
Lombok 执行流程:
Lombok 原理及简单实现 - 图2

Lombok 这款插件正是依靠可插件化的 Java 自定义注解处理 API(JSR 269: Pluggable Annotation Processing API)来实现。在 Javac 编译阶段利用“Annotation Processor”,对自定义的注解进行预处理后生成真正在 JVM 上面执行的“Class文件”。

Java插件化处理API

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

使用 Annotation Processing 自定义注解是在 编译阶段 进行修改,而 JDK 的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。

3. 实现Getter注解

按照lombok的基本流程,接下来自己实现一个类似功能的Getter注解

  • 定义编译期的注解
  • 利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器
  • 利用tools.jar的javac api处理AST(抽象语法树)
  • 将功能注册进jar包

1. 项目依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.imooc</groupId>
  7. <artifactId>mylombok</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <dependencies>
  10. <dependency>
  11. <groupId>com.sun</groupId>
  12. <artifactId>tools</artifactId>
  13. <version>1.8</version>
  14. <scope>system</scope>
  15. <systemPath>${java.home}/../lib/tools.jar</systemPath>
  16. </dependency>
  17. </dependencies>
  18. <build>
  19. <plugins>
  20. <plugin>
  21. <groupId>org.apache.maven.plugins</groupId>
  22. <artifactId>maven-compiler-plugin</artifactId>
  23. <version>3.1</version>
  24. <configuration>
  25. <source>1.8</source>
  26. <target>1.8</target>
  27. </configuration>
  28. </plugin>
  29. </plugins>
  30. </build>
  31. </project>

主要是tools.jar包的依赖和编译环境的配置。

2. 创建Getter注解

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.SOURCE) //注解在编译期起作用
  3. public @interface Getter {
  4. }
  1. // Retention注解上面有一个属性value,它是RetentionPolicy类型的枚举类,RetentionPolicy枚举类中有三个值。
  2. public enum RetentionPolicy {
  3. SOURCE,
  4. CLASS,
  5. RUNTIME
  6. }
  • SOURCE修饰的注解:修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中
  • CLASS修饰的注解:表示注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候
  • RUNTIME修饰的注解:表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时。所以它能够通过反射调用,所以正常运行时注解都是使用的这个参数

Target注解上面也有个属性value,它是ElementType类型的枚举。是用来修饰此注解作用在哪的。

  1. public enum ElementType {
  2. TYPE,
  3. FIELD,
  4. METHOD,
  5. PARAMETER,
  6. CONSTRUCTOR,
  7. LOCAL_VARIABLE,
  8. ANNOTATION_TYPE,
  9. PACKAGE,
  10. TYPE_PARAMETER,
  11. TYPE_USE
  12. }

3. 创建Getter注解处理器

我们要定义注解处理器的话,那么就需要继承AbstractProcessor类。继承完以后基本的框架类型如下

  1. import javax.annotation.processing.AbstractProcessor;
  2. @SupportedSourceVersion(SourceVersion.RELEASE_8)
  3. @SupportedAnnotationTypes("aboutjava.annotion.MyGetter")
  4. public class MyGetterProcessor extends AbstractProcessor {
  5. @Override
  6. public synchronized void init(ProcessingEnvironment processingEnv) {
  7. super.init(processingEnv);
  8. }
  9. @Override
  10. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  11. return true;
  12. }
  13. }

我们可以看到在子类中上面有两个注解,注解描述如下

  • @SupportedSourceVersion:表示所支持的Java版本
  • @SupportedAnnotationTypes:表示该处理器要处理的注解

继承了父类的两个方法,方法描述如下

  • init方法:主要是获得编译时期的一些环境信息
  • process方法:在编译时,编译器执行的方法。也就是我们写具体逻辑的地方
  1. import javax.annotation.processing.AbstractProcessor;
  2. @SupportedAnnotationTypes("com.imooc.mylombok.Getter")
  3. @SupportedSourceVersion(SourceVersion.RELEASE_8)
  4. public class GetterProcessor extends AbstractProcessor {
  5. // 打印log
  6. private Messager messager;
  7. // 抽象语法树
  8. private JavacTrees trees;
  9. // 封装了创建AST节点的一些方法
  10. private TreeMaker treeMaker;
  11. // 提供了创建标识符的一些方法
  12. private Names names;
  13. // 初始化方法
  14. @Override
  15. public synchronized void init(ProcessingEnvironment processingEnv) {
  16. super.init(processingEnv);
  17. this.messager = processingEnv.getMessager();
  18. this.trees = JavacTrees.instance(processingEnv);
  19. Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
  20. this.treeMaker = TreeMaker.instance(context);
  21. this.names = Names.instance(context);
  22. }
  23. // 真正处理注解的方法
  24. @Override
  25. public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  26. // 获取所有包含Getter注解的类
  27. Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
  28. set.forEach(element -> {
  29. JCTree jcTree = trees.getTree(element);
  30. jcTree.accept(new TreeTranslator() {
  31. @Override
  32. public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
  33. List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
  34. // 获取所有属性
  35. for (JCTree tree : jcClassDecl.defs) {
  36. if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
  37. JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
  38. jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
  39. }
  40. }
  41. // 为每一个属性创建get方法
  42. jcVariableDeclList.forEach(jcVariableDecl -> {
  43. messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
  44. jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
  45. });
  46. super.visitClassDef(jcClassDecl);
  47. }
  48. });
  49. });
  50. return true;
  51. }
  52. // 创建getter方法
  53. private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
  54. ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
  55. statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
  56. JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
  57. return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
  58. }
  59. // 获取方法名
  60. private Name getNewMethodName(Name name) {
  61. String s = name.toString();
  62. return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
  63. }
  64. }

4. 打包

在resources文件夹下创建META-INF文件夹,META-INF下创建services文件夹,services下创建文件名为javax.annotation.processing.Processor的文本文件,内容为com.imooc.mylombok.GetterProcessor。

添加这个文件是为了将自己添加为processor,方便其他项目调用,但自己编译时又不需要processor,就死循环了,自己在编译时不能添加services文件夹,但又需要打的包里有services文件夹,这时候需要配置maven插件。

  1. <build>
  2. <resources>
  3. <resource>
  4. <directory>src/main/resources</directory>
  5. <excludes>
  6. <exclude>META-INF/**/*</exclude>
  7. </excludes>
  8. </resource>
  9. </resources>
  10. <plugins>
  11. <plugin>
  12. <groupId>org.apache.maven.plugins</groupId>
  13. <artifactId>maven-compiler-plugin</artifactId>
  14. <version>3.1</version>
  15. <configuration>
  16. <source>1.8</source>
  17. <target>1.8</target>
  18. </configuration>
  19. </plugin>
  20. <plugin>
  21. <groupId>org.apache.maven.plugins</groupId>
  22. <artifactId>maven-resources-plugin</artifactId>
  23. <version>2.6</version>
  24. <executions>
  25. <execution>
  26. <id>process-META</id>
  27. <phase>prepare-package</phase>
  28. <goals>
  29. <goal>copy-resources</goal>
  30. </goals>
  31. <configuration>
  32. <outputDirectory>target/classes</outputDirectory>
  33. <resources>
  34. <resource>
  35. <directory>${basedir}/src/main/resources/</directory>
  36. <includes>
  37. <include>**/*</include>
  38. </includes>
  39. </resource>
  40. </resources>
  41. </configuration>
  42. </execution>
  43. </executions>
  44. </plugin>
  45. </plugins>
  46. </build>

执行打包

  1. mvn clean package

5. 使用Getter注解

  1. import com.imooc.mylombok.Getter;
  2. @Getter
  3. public class App {
  4. private String value;
  5. private String value2;
  6. public App(String value) {
  7. this.value = value;
  8. }
  9. public static void main(String[] args) {
  10. App app = new App("it works");
  11. System.out.println(app.getValue());
  12. }
  13. }

编译App

  1. javac -cp mylombok-1.0-SNAPSHOT.jar App.java

运行APP

  1. java App

结果为

  1. it works

符合预期,说明Getter注解生效了,看一下App的反编译结果,确实生成了get方法

  1. public class
  2. public String getValue2(){
  3. return value2;
  4. }
  5. public String getValue(){
  6. return value;
  7. }
  8. public App(String s){
  9. value = s;
  10. }
  11. public static void main(String args[]){
  12. App app = new App("it works");
  13. System.out.println(app.getValue());
  14. }
  15. private String value;
  16. private String value2;
  17. }