写点什么

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

作者:Java你猿哥
  • 2023-05-29
    湖南
  • 本文字数:4734 字

    阅读完需:约 16 分钟

平时都是在用 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
复制代码

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

编译

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

程序包 com.sun.tools.javac.tree 不可见
复制代码


<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;
@Getterpublic 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:29java: 找不到符号  符号:   方法 getName()  位置: 类型为org.example.Main的变量 m
复制代码

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


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


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

xiao ming
复制代码

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

总结

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


用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
防lombok实现一个Getter注解,AbstractProcessor实例_Java_Java你猿哥_InfoQ写作社区