8 Feb 2018

自制Lombok功能实现

介绍

Lombok是一个开源项目,通过添加注解,自动生成一些方法,但却降低了源代码文件的可读性和完整性
使用方法参考
本文通过小demo实现自制Lombok实现通过注解,生成自己的方法

基本流程

  1. 定义编译期的注解
  2. 利用JSR269 api(Pluggable Annotation Processing API )(允许在编译时指定一个processor类来对编译阶段的注解进行干预)创建编译期的注解处理器
  3. 利用tools.jar的javac api处理AST(抽象语法树)
  4. 将功能注册进jar包

创建注解及处理器

新建maven项目(lombokDemo)

pom.xml

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.jun</groupId>
   <artifactId>lombokDemo</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>lombokDemo</name>
   <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>
   <dependencies>
       <!--tools.jar在jdk的lib下面,因此scope是system,${java.home}表示jre位置-->
       <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.7.0</version>
               <configuration>
                   <source>1.8</source>
                   <target>1.8</target>
               </configuration>
           </plugin>
       </plugins>
   </build>

定义注解 MyLombok.java

@Target({ElementType.TYPE})//对类的注解
@Retention(RetentionPolicy.SOURCE)//只在编译期起作用
public @interface MyLombok { }

注解处理器

@SupportedAnnotationTypes("com.jun.MyLombok")//该处理器需要处理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8)//该处理器支持的源码版本
public class MyLombokProcessor extends AbstractProcessor {
    //在编译期打log用
    private Messager messager;
    //提供了待处理的抽象语法树
    private JavacTrees trees;
    //封装了创建AST节点的一些方法
    private TreeMaker treeMaker;
    //提供了创建标识符的方法
    private Names names;
    //通过ProcessingEnvironment来获取编译阶段的一些环境信息
    @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) {
        //利用roundEnv的getElementsAnnotatedWith方法过滤出被MyLombok注解标记的类,并存入set
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(MyLombok.class);
        //遍历set并生成jCTree这个语法树
        set.forEach(element -> {
            JCTree jcTree = trees.getTree(element);
            jcTree.accept(new TreeTranslator() {
                //这个方法处理遍历语法树得到的类定义部分jcClassDecl
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    //保存类的成员变量
                    //List 是 package com.sun.tools.javac.util
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    //遍历jcTree的所有成员(包括成员变量和成员函数和构造函数),过滤出其中的成员变量,并添加进jcVariableDeclList
                    for (JCTree tree : jcClassDecl.defs) {
                        if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
                    jcVariableDeclList.forEach(jcVariableDecl -> {
                        messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
                        //将jcVariableDeclList的所有变量转换成需要添加的方法,并添加进jcClassDecl的成员中
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetMethodDecl(jcVariableDecl));
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetMethodDecl(jcVariableDecl));
                    });
                    //调用默认的遍历方法遍历处理后的jcClassDecl
                    //利用上面的TreeTranslator去处理jcTree
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        return true;
    }
    private JCTree.JCMethodDecl makeGetMethodDecl(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()));
    }
    private JCTree.JCMethodDecl makeSetMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        // 添加调用语句" this.setXXX(xxx); "
//        statements.append(
//                treeMaker.Exec(
//                        treeMaker.Apply(
//                                List.nil(),
//                                treeMaker.Select(
//                                        treeMaker.Ident(names.fromString("this")),
//                                        getNewMethodName(jcVariableDecl.getName())
//                                ),
//                                List.of(treeMaker.Ident(jcVariableDecl.getName()))
//                        )
//                )
//        );
        // this.XXX=xxx
        statements.append(
                treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName())))
        );
        // 添加返回语句 " return this; "
//      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),// public方法
                setNewMethodName(jcVariableDecl.getName()),// 方法名称
                null,// 方法返回的类型
                List.nil(),// 泛型参数
                List.nil(),// 方法参数
                List.nil(),// throw表达式
                body,// 方法体
                null// 默认值
        );
    }
    private Name setNewMethodName(Name name) {
        String s = name.toString();
        return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }
}

创建测试类 Test.java

//@MyLombok
public class Test {
    private String name;
    private int age;
    public Test(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String toString(){
       return String.format("{ '%s':%s ,'%s':%d}", "name", this.name, "age", this.age);
    }
    public static void main(String[] args) {
        Test app = new Test("大明",12);
        System.out.println(app.toString());
    }
}

编译测试

# 创建文件夹
mkdir classes
# 拷贝 %JAVA_HOME%\lib\tools.jar 至项目
# 编译注解及其处理器
javac  -encoding utf-8  -cp tools.jar com/jun/MyLombok*.java -d classes/
# 编译Test
javac -cp classes -d classes -processor com.jun.MyLombokProcessor com/jun/Test.java
# 反编译Test.class
javap -p classes/com/jun/Test.class
# 运行Test
java -cp classes com.jun.Test

项目打包

  1. 删除或注释测试文件
  2. mvn clean install

测试项目

新建maven项目(myLombokTest)

pom.xml

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.jun</groupId>
   <artifactId>myLombokTest</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>myLombokTest</name>
   <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>
   <dependencies>
      <!-- lombokDemo.jar 放到resources/lib下,也可以放到私服-->
       <dependency>
           <groupId>com.jun</groupId>
           <artifactId>lombokDemo</artifactId>
           <version>1.0-SNAPSHOT</version>
           <scope>system</scope>
           <systemPath>${project.basedir}/src/main/resources/lib/lombokDemo.jar</systemPath>
       </dependency>
   </dependencies>

新建测试文件 Test.java

@MyLombok
public class Test {
    private String name;
    private int age;
    public Test(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return String.format("{ '%s':%s ,'%s':%d}", "name", this.name, "age", this.age);
    }
    public static void main(String[] args) {
        Test app = new Test("大明",12);
        System.out.println(app.toString());
    }
}

运行测试


Tags: