平时都是在用 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;
@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
再次 编译项目 , 如果发现下面这几行 我们在前面代码里打印的 LOG,就说明 GetterProcessor 生效了。
接下来 就可以运行 Main 类。输出
也可以通过 java -cp .\target\MyLombokTest-1.0.jar org.example.Main 来运行 这个测试类。
总结
在编译时通过注解 修改代码,这样的操作平时我们很少会去实现。更多是使用。在写这个 demo 时,很多 API 不熟悉。也是通过网上的资料加上 lombok 的代码,边看边试着做。还是比较费时间。而且这个工具包没有办法调试,写起来就很麻烦。只能一次次编译,找错误,反复尝试
评论