likes
comments
collection
share

防lombok实现一个Getter注解,AbstractProcessor实例

作者站长头像
站长
· 阅读数 44

平时都是在用lombok,毕竟用起来简单省事。java还是太繁琐了,最近也想着是不是可以自己简化一下java的相关写法,就想着了解一下lombok. 这里记录一下 自己防lombok实现一个@Getter注解。主要是因为lombok的源码太多。看起来实在是有点费事。

先创建一个工程

用自己习惯的方式,创建一个maven工程。我这里是基于java 17实现的。然后 定义一个Getter注解。

package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

接下来是 实现 GetterProcessor 类。

package org.example;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.Trees;

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  com.sun.tools.javac.tree.JCTree;
import  com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import  com.sun.tools.javac.code.Flags;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class GetterProcessor extends AbstractProcessor {

    private ProcessingEnvironment processingEnv;
    private Messager messager;

    private Trees trees;

    private TreeMaker treeMaker;

    private Names names;


    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE,  " -- note  getter init --");
        ProcessingEnvironment unwrappedProcessingEnv = jbUnwrap(ProcessingEnvironment.class, processingEnv);
        this.trees = Trees.instance(unwrappedProcessingEnv);

        Context context = ((JavacProcessingEnvironment) unwrappedProcessingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    private static <T> T jbUnwrap(Class<? extends T> iface, T wrapper) {
        T unwrapped = null;
        try {
            final Class<?> apiWrappers = wrapper.getClass().getClassLoader().loadClass("org.jetbrains.jps.javac.APIWrappers");
            final Method unwrapMethod = apiWrappers.getDeclaredMethod("unwrap", Class.class, Object.class);
            unwrapped = iface.cast(unwrapMethod.invoke(null, iface, wrapper));
        }
        catch (Throwable ignored) {}
        return unwrapped != null? unwrapped : wrapper;
    }
    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
        set.forEach(element -> {
            Tree jcTree = trees.getTree(element);
            messager.printMessage(Diagnostic.Kind.NOTE, element.getSimpleName() + " has been processed");
            jcTree.accept(new SimpleTreeVisitor<Void, Void>(){
                @Override
                public Void visitClass(ClassTree node, Void p) {
                    messager.printMessage(Diagnostic.Kind.NOTE, node.getSimpleName() + " visitClass");
                    List<JCTree.JCVariableDecl> list = new ArrayList<>();
                    if(node instanceof  JCClassDecl){
                        JCClassDecl classDecl = (JCClassDecl) node;
                        for (JCTree tree : classDecl.defs) {
                            if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                                JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                                list.add(jcVariableDecl);
                            }
                        }
                        list.forEach(jcVariableDecl -> {
                            messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " variable");
                            classDecl.defs = classDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                        });
                    }

                    return defaultAction(node, p);
                }
            }, null);

        });

        return true;
    }

    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,
                com.sun.tools.javac.util.List.nil(), com.sun.tools.javac.util.List.nil(), com.sun.tools.javac.util.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()));
    }

}

到这里,主要代码就结束了。

当然这个时候在idea里 就开始报错了。提示:

Package 'com.sun.tools.javac.tree' is declared in module 'jdk.compiler', which does not export it to the unnamed module

防lombok实现一个Getter注解,AbstractProcessor实例

点一下 报错里的提示 : Add '--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' to module compile options. 这是因为java 11后 ,做了模块控制。把其它的几个报错,也点一下。

编译

这个时候 我们直接 通过 mvn install 去编译。会发现 还是会报错

程序包 com.sun.tools.javac.tree 不可见

我们需要在pom.xml里配置一下插件

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <compilerArgs>
                    <arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
                    <arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
                    <arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
                    <arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>

                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

这里也是 把 几个 包导出到 ALL-UNNAMED。 这样就可以编译通过了。

让自定义的 GetterProcessor 生效

想让自定义的 GetterProcessor 生效。需要在resource目录下增加 META-INF目录,再在META-INF下增加services目录。在 META-INF/services 目录下新建 javax.annotation.processing.Processor文件。在里面写上这一行。

org.example.GetterProcessor

这样就知道 需要使用 org.example.GetterProcessor 来处理代码了。

我们再次使用 mvn install 来编译自定义的 GetterProcessor 。

下面是完整的项目文件结构

│  pom.xml
├─src
│  ├─main
│  │  ├─java
│  │  │  └─org
│  │  │      └─example
│  │  │              Getter.java
│  │  │              GetterProcessor.java
│  │  └─resources
│  │      └─META-INF
│  │          └─service
│  │                  javax.annotation.processing.Processor

创建测试项目

创建一个测试项目 MyLombokTest 。 引入前面编译好的包

<?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>org.example</groupId>
    <artifactId>MyLombokTest</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>MyLombok</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
</project>

创建一个测试类 Main

package org.example;

@Getter
public class Main {
    private String name;

    public Main(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Main m = new Main("xiao ming");
        System.out.println(m.getName());
    }
}

然后我们在idea里 ctrl+ f9 编译工程。就报错了。

E:\code\datecenter\MyLombokTest\src\main\java\org\example\Main.java:13:29
java: 找不到符号
  符号:   方法 getName()
  位置: 类型为org.example.Main的变量 m

这是因为没配置 processor . 到 settings 里配置一下 annotation processors. 加上 org.example.GetterProcessor

防lombok实现一个Getter注解,AbstractProcessor实例

再次 编译项目 , 如果发现下面这几行 我们在前面代码里打印的LOG,就说明 GetterProcessor 生效了。

防lombok实现一个Getter注解,AbstractProcessor实例

接下来 就可以运行 Main类。输出

xiao ming

也可以通过 java -cp .\target\MyLombokTest-1.0.jar org.example.Main 来运行 这个测试类。

总结

在编译时通过注解 修改代码,这样的操作平时我们很少会去实现。更多是使用。在写这个demo时,很多API不熟悉。也是通过网上的资料加上lombok的代码,边看边试着做。还是比较费时间。而且这个工具包没有办法调试,写起来就很麻烦。只能一次次编译,找错误,反复尝试

参考:

blog.mythsman.com/post/5d2c11…

转载自:https://juejin.cn/post/7238027797314355237
评论
请登录