Lombok原理分析及简单实现

使用

maven引入依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.14</version>
</dependency>
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {

  private String name;
}

自动生成get,set方法,全参构造器,无参构造器

public class Client {
  public static void main(String[] args) {
    User user = new User();
    user.setName("lisi");
    System.out.println(user.getName());
  }
}

原理

通过网上查资料,lombok的基本流程应该是

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

接下来自己实现一个类似功能的Getter注解。

项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>mylombok</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

    </dependencies>

    <build>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

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

创建Getter注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}

注解在编译期起作用。

创建Getter注解处理器

@SupportedAnnotationTypes("com.imooc.mylombok.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {
 // 打印log
  private Messager messager;
 // 抽象语法树
  private JavacTrees trees;
 // 封装了创建AST节点的一些方法
  private TreeMaker treeMaker;
 // 提供了创建标识符的一些方法
  private Names names;

// 初始化方法
  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    this.messager = processingEnv.getMessager();
    this.trees = JavacTrees.instance(processingEnv);
    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
    this.treeMaker = TreeMaker.instance(context);
    this.names = Names.instance(context);
  }

// 真正处理注解的方法
  @Override
  public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取所有包含Getter注解的类
    Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
    set.forEach(element -> {
      JCTree jcTree = trees.getTree(element);
      jcTree.accept(new TreeTranslator() {
        @Override
        public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
          List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
	// 获取所有属性
          for (JCTree tree : jcClassDecl.defs) {
            if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
              JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
              jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
            }
          }
	// 为每一个属性创建get方法
          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;
  }

// 创建getter方法
  private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {

    ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
    statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
    JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
  }

// 获取方法名
  private Name getNewMethodName(Name name) {
    String s = name.toString();
    return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
  }
}

打包

在resources文件夹下创建META-INF文件夹,META-INF下创建services文件夹,services下创建文件名为javax.annotation.processing.Processor的文本文件,内容为com.imooc.mylombok.GetterProcessor。
添加这个文件是为了将自己添加为processor,方便其他项目调用,但自己编译时又不需要processor,就死循环了,自己在编译时不能添加services文件夹,但又需要打的包里有services文件夹,这时候需要配置maven插件。

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>META-INF/**/*</exclude>
                </excludes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>process-META</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/classes</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${basedir}/src/main/resources/</directory>
                                    <includes>
                                        <include>**/*</include>
                                    </includes>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

执行打包

mvn clean package

使用Getter注解

import com.imooc.mylombok.Getter;

@Getter
public class App {
  private String value;

  private String value2;

  public App(String value) {
    this.value = value;
  }

  public static void main(String[] args) {
    App app = new App("it works");
    System.out.println(app.getValue());
  }
}

编译App

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

运行APP

java App

结果为

it works

符合预期,说明Getter注解生效了,看一下App的反编译结果

public class App{

    public String getValue2(){
        return value2;
    }

    public String getValue(){
        return value;
    }

    public App(String s){
        value = s;
    }

    public static void main(String args[]){
        App app = new App("it works");
        System.out.println(app.getValue());
    }

    private String value;
    private String value2;
}

确实生成了get方法。

posted @ 2020-07-11 10:48  strongmore  阅读(3679)  评论(4编辑  收藏  举报