lombok 是一个很是神奇的 java 类库,会利用注解自动生成 java Bean 中烦人的 Getter、Setting,还能自动生成 logger、ToString、HashCode、Builder 等 java
特点的函数或是符合设计模式的函数,可以让你 java Bean 更简洁,更美观。html
来先看下使用 lombok 后的 java bean 看起来是怎样的java
@Data @AllArgsConstructor public class User { private Long id; private String name; private Integer age; public static void main(String[] args) { User user = new User(1L,"张三",18); System.out.println("toString:"+user); System.out.println("name:"+user.getName()); } }
输出以下json
toString():User(id=1, name=张三, age=18) getName:张三
看到了吗,仅仅在类中添加 @Data 注解就能作到自动生成 Getter
和 ToString
函数了! 仅仅添加了 @AllArgsConstructor
注解就不再用写那些让你心烦的构造函数了!设计模式
我第一次使用 lombok 的时候就很喜欢它了。以为它的思想很是好的,便是不该该花时间去写重复的代码,应该让之自动化。api
固然自动化的手段是什么也很重要,我能够经过 ide 的生成器功能、或者是本身写的代码生成器,自动生成 Getter、Setting、ToString,就算不用 lombok 也是能够作到的。而我喜欢 lombok 最主要缘由就在于它会让代码更简洁、阅读起来更清晰。安全
可能会有人说这玩意只适合我的项目,不适合大型合做。。。但知乎有位大神说过亚马逊(重度使用 java 的公司) 内部的项目都在用。无论如何仍是先了解再决定吧,我我的仍是很喜欢用这种方式。bash
上面的介绍了一下 lombok 有点简陋,因此下面会介绍得更详细的一点。固然也许会有读者对 lombok 这种像是魔法的写法感到好奇,究竟它是怎么自动生成代码的呢?因此文章的后面会对 lombok 进行简单的探讨,但愿读者会喜欢。服务器
得现代的依赖管理 ,引入 lombok 依赖及其简单数据结构
使用 meavn 的朋友在 pom.xml 文件中添加依赖便可app
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> <scope>provided</scope> </dependency> </dependencies>
用 gradle 的朋友 在 build.gradle 在添加依赖便可
repositories { mavenCentral() } dependencies { compileOnly 'org.projectlombok:lombok:1.18.6' annotationProcessor 'org.projectlombok:lombok:1.18.6' }
你看 pom.xml 的依赖的做用域(scope) 是 provided,也就是说 lombok 是在编译的时候才起做用的。所以 idea 在正确使用 lombok 的时候也会报错的
因此你要安装 lombok 插件才能正常使用。
类型 | 解释 |
---|---|
val,var | 神奇的类型推到,能够表明任意类型 |
@Getter and @Setter | |
@ToString | |
@EqualsAndHashCode | |
@NonNull | |
@AllArgsConstructor、@RequiredArgsConstructor、@NoArgsConstructor | 构造函数部分,针对不一样状况的构造函数 |
@Data | 至关于 @Getter + @Setter + @ToString + @EqualsAndHashCode + RequiredArgsConstructor |
@Value | 类变成只读模式 |
@Builder | builder 模式,会建立内 Builder |
@Singular | 要配合 builder 使用,会对(List、Set)等生成更方便函数 |
@Cleanup | 告别烦人的释放的资源 |
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j @CommonsLog, @JBossLog, @Flogger |
不一样框架的日志注解 |
@SneakyThrows | 偷偷摸摸地抛出异常 |
@Delegate | 带实验性质的,能很是方便实现代理模式 |
@Accessors | 带实验性质的存取器 |
@Wither | 带实验性质的,根据被修饰的成员变量建立类 |
能够表示任何类型!
var 能够用来表示变量,相似其余语言中的 let
val 能够用来表示常量(final),相似其余语言中的 const
var str = "hello world"; val list = Arrays.asList(1,2,3,4); System.out.println(str); for(val item : list){ System.out.printf("%d\t",item); }
等价于
String str = "hello world"; final List<Integer> list = Arrays.asList(1,2,3,4); System.out.println(str); for(final Integer item : list){ System.out.printf("%d\t",item); }
添加了注解后会根据字段生成对应的 get、set 函数,能够修饰成员变量或者类
@Getter @Setter public class User { private Long id; private String name; private Integer age; }
灵活的 lombok 能够经过,下面的方式指定访问级别(PUBLIC、PROTECTED、PACKAGE、PRIVATE)
@Getter @Setter public class User { private Long id; private String name; @Setter(AccessLevel.PROTECTED) private Integer age; }
@ToString public class User { private Long id; private String name; private Integer age; }
ToString 生成后代码大概以下
public String toString() { return "User(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")"; }
选项:
一、@ToString(includeFieldNames=false) 不显示变量名,会直接输出值
public String toString() { return "User(" + this.id + ", " + this.name + ", " + this.age + ")"; }
二、@ToString(exclude = {"age"}) 生成的结果会排出 age 变量
public String toString(){ return "User(id=" + this.id + ", name=" + this.name + ")"; }
三、@ToString(of = {"id","name"}) 生成的结果包括
public String toString(){ return "User(id=" + this.id + ", name=" + this.name + ")"; }
只能用于修饰类。
@EqualsAndHashCode public class User { //... }
和 ToString 相似,能够用 of 以及 exclude 来排出成员变量
能够用于成员变量、本地变量、参数、方法
@Setter public class User { private Long id; @NonNull private String name; private Integer age; }
setName 函数实际上会变成这样
public void setName(@NonNull String name) { if (name == null) { throw new NullPointerException("name is marked @NonNull but is null"); } else { this.name = name; } }
这三者都是处理构造函数的注解,都只能修饰类,都能经过staticName
建立静态工厂方法,使用access
控制访问级别。
不一样之处在于 @AllArgsConstructor
会把全部的成员变量都归入到构造函数中, @RequiredArgsConstructor
只会把 final
和 @NonNull
修饰的成员变量归入、@NoArgsConstructor
全部的成员变量都不会归入到构造函数。
构造函数会包含全部字段
@AllArgsConstructor public class User { private Long id; private String name; private Integer age; }
会自动生成
public User(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; }
关于 staticName
和 access
的选项,能够看下面的例子
@AllArgsConstructor(staticName = "of",access = AccessLevel.PRIVATE) public class User { private Long id; private String name; private Integer age; }
会看到构造函数和静态工厂函数的访问级别都变成 private
了
public class User { private Long id; private String name; private Integer age; private User(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } private static User of(Long id, String name, Integer age) { return new User(id, name, age); } }
用 final
修饰和 @NonNull
修饰的参数才会加入构造函数
@RequiredArgsConstructor public class User { @NonNull private Long id; private final String name; private Integer age; }
生成的结果大概是这样
public class User { @NonNull private Long id; private final String name; private Integer age; public User(@NonNull Long id, String name) { if (id == null) { throw new NullPointerException("id is marked @NonNull but is null"); } else { this.id = id; this.name = name; } } }
顾名思义,使用 @NoArgsConstructor
会生成没有参数的构造函数
但若是是用
final
修饰的成员函数呢?
答:这样会编译出错的,除非是用 @NoArgsConstructor(force=true)
,那么全部的 final 字段会被定义为0,false,null等。
若是使用使用的是
@NonNull
修饰的成员字段呢?那么使用无参数的构造函数构造出来的实例成员变量不就是 null 了吗?不就矛盾了吗?
答:是的。。。
好比
@NoArgsConstructor @Getter public class User { private Long id ; private @NonNull String name; private Integer age; public static void main(String[] args) { System.out.println(new User().getName()); } }
输出结果是 null
所以若是有 @NonNull
修饰的成员的变量就不要用 @NoArgsConstructor
修饰类
@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor 就是那么的方便
选项,能够经过 staticConstructor
建立静态工厂函数
@Data(staticConstructor = "of") public class User { private Long id ; private @NonNull String name; private Integer age; }
将类变成只读模式。会让全部类成员变量都变成 final,而后 + @RequiredArgsConstructor + @ToString + @EqualsAndHashCode
面对复杂的数据结构,使用 builder 模式能够抽离复杂的构造方式,能保证线程安全,我在这篇文章中也有对 Builder 的进行粗略的探讨。
使用 Builder 模式很爽,好比是这样的
User user = User.builder().id(1L) .name("张三") .age(12).build();
这样虽然好爽,但
代价是什么呢?
就是好多冗余的东西要写。好比是这样
public class User { private Long id; private String name; private Integer age; User(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public static User.UserBuilder builder() { return new User.UserBuilder(); } public static class UserBuilder { private Long id; private String name; private Integer age; UserBuilder() { } public User.UserBuilder id(Long id) { this.id = id; return this; } public User.UserBuilder name(String name) { this.name = name; return this; } public User.UserBuilder age(Integer age) { this.age = age; return this; } public User build() { return new User(this.id, this.name, this.age); } public String toString() { return "User.UserBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")"; } } }
用 lombok 后
@Builder public class User { private Long id; private String name; private Integer age; }
清晰明了,爽!
可是这里有个很严重的问题,就是不符合 java bean 的规范,java bean 要求有一个无参数的构造函数的。不符号 java bean 要求会有什么后果能?好比:json 字符不能反序列化成 java 对象。
解决方式是写成这样,要同时写上 @AllArgsConstructor 和 @NoArgsConstructor 才行
@Builder @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; }
Singular 要和 Builder 一块儿使用的,会对 List、Set 等集合类生出、处理 addOne、addAll、clear 方法
好比源码是这样的
@Builder public class User { private Long id; private String name; private Integer age; private @Singular Set<String> Girlfriends; }
生成的代码 User 的静态内部类 UserBuilder 会在增添
public User.UserBuilder Girlfriend(String Girlfriend) { if (this.Girlfriends == null) { this.Girlfriends = new ArrayList(); } this.Girlfriends.add(Girlfriend); return this; } public User.UserBuilder Girlfriends(Collection<? extends String> Girlfriends) { if (this.Girlfriends == null) { this.Girlfriends = new ArrayList(); } this.Girlfriends.addAll(Girlfriends); return this; } public User.UserBuilder clearGirlfriends() { if (this.Girlfriends != null) { this.Girlfriends.clear(); } return this; }
处理烦人的资源释放的神奇手段
在 java 7 以前的资源释放是使用 try-catch-finally 的方式处理的,繁琐而容易出错
FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(new File("a.txt")); fos = new FileOutputStream("b.txt"); byte[] buffer = new byte[1024]; int len; while ( (len = fis.read(buffer)) != -1){ fos.write(buffer,0,len); } }catch (IOException e){ e.printStackTrace(); }finally { if(fis!=null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
固然 java 7 以后是使用简化了一下,实现了 Closeable
的类能够用 try-with-resource 自动关闭链接
public static void main(String[] args) { try ( FileInputStream fis = new FileInputStream(new File("a.txt")); FileOutputStream fos = new FileOutputStream("b.txt");){ byte[] buffer = new byte[1024]; int len; while ( (len = fis.read(buffer)) != -1){ fos.write(buffer,0,len); } } catch (IOException e) { e.printStackTrace(); } }
而 lombok 则只需添加 @Cleanup 则能够完成释放资源,但一样须要类自己实现了 Closeable
接口
try { @Cleanup FileInputStream fis = new FileInputStream(new File("a.txt")); @Cleanup FileOutputStream fos = new FileOutputStream("b.txt"); byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); }
java 有不少日志的框架,这里用就只以 Log4j2 为例了
@Log4j2 public class User { }
会生成
public class User { private static final Logger log = LogManager.getLogger(User.class); }
算是一个比较有争议的,意思是近悄悄地抛出异常,要谨慎使用。check exception 会转成 unchecked 的。
@SneakyThrows(FileNotFoundException.class) public static void read() { FileReader reader = new FileReader("sdf.txt"); }
调用的使用也不用再声明throws FileNotFoundException
public static void main(String[] args) { read(); }
利用 @Delegate 能很是方便地实现代理模式。下面用个场景介绍一下,好比:有台代理服务器,有台文件服务器,而咱们只能经过代理服务去访问文件服务器。
最终调用大概如此
public class Main { public static void main(String[] args) throws IOException { ProxyServer proxyServer = new ProxyServer(); proxyServer.loadFile("avatar.png"); } }
假设获取接口是这样的
public interface Server { byte[] loadFile(String fileName) throws IOException; }
文件服务器的简单实现是如此
public class FileServer implements Server { @Override public byte[] loadFile(String fileName) throws IOException { return Files.readAllBytes(new File(fileName).toPath()); } }
不用 lombok 时要这样
public class ProxyServer implements Server{ FileServer realServer = new FileServer(); @Override public byte[] loadFile(String fileName) throws IOException { return realServer.loadFile(fileName); } }
代理服务器利用 @Delegate 便可,简单快捷
public class ProxyServer implements Server{ @Delegate FileServer realServer = new FileServer(); }
使用 @Accessors 生成 Setter 会是链式的
@Accessors(fluent = true) @Setter public class User { private Long id; private String name; private Integer age; public User() { } public User id(Long id) { this.id = id; return this; } public User name(String name) { this.name = name; return this; } public User age(Integer age) { this.age = age; return this; } }
它有三个选项:(以 name 成员变量为例)
String setName(String name)
; 若是是 true, setter 是生成的是 User name(String name)
,返回的是 this,是种链式 Setter,同时 chain 会是 true。User setName(String name)
。不然的是void setName(String name)
用于修饰成员变量,能够根据用于被修饰的成员变量建立一个对象。
@AllArgsConstructor public class User { private Long id; @Wither private String name; private Integer age; }
生成后的代码
public class User { private Long id; private String name; private Integer age; public User(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public User withName(String name) { return this.name == name ? this : new User(this.id, name, this.age); } }
或者你会好奇,这么神奇的魔法原理是什么呢?函数是如何生成的?
想一想编译原理 ,想一想 java 文件编译成 class 文件的过程,
Java 源码编译由如下三个过程组成:
那么 lombok 是在那里添加要插入的代码呢?估计是注解处理处理部分吧。
要如何生成呢?猜一下,估计多是在注解处理时期 javac 能调用一个借口处理注解,咱们并能够从中获取当 AST 树,而后就能够根据咱们的想法,直接修改语法树了
在网上搜索的时候找到这样一篇文章《Project Lombok: Creating Custom Transformations》 及 stackoverflow 的 问题正好能解决咱们的疑惑
lombok 的执行过程以下图
和我猜测的差很少
上面一个答案说根据 JSR269 提案的 process 的 javax.annotation.processing.AbstractProcessor api 可能弄出来,还有一个回答说 lombok 作的东西比 process 多,还有针对 eclipse、javac 处理的 handle。找了一下文章看,确实如此,不只设计的接口好,并且有丰富的例子能够给你参考。有关 java ast 的 jcTree 是有文档,但我只找到 api 文档,没什么例子。但我下面仍是会用 a
关于单例模式,我在以前的博客中也探讨过了。
我选取其中的一个例子吧。但愿生成的结果是这样的
public class SingletonRegistry { private SingletonRegistry() {} private static class SingletonRegistryHolder { private static SingletonRegistry instance = new SingletonRegistry(); } public static SingletonRegistry getInstance() { return SingletonRegistryHolder.instance; } // other methods }
而源码只需这样就能够了,
@Singleton public class SingletonRegistry{}
在 com.jojo.annotation.processor 目录下建立注解,其中
@Target(ElementType.TYPE)
意思是说注解只能修饰类,不能修饰方法、变量等,@Retention(RetentionPolicy.SOURCE)
意思是说,注解保留范围是源代码,也就是在编译以后就看不到了,在 class 文件看不到,运行的时候用反射拿也就拿不到了@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Singleton { }
万物起头 Hello World。咱们要确认下,实现 AbstractProcessor 接口后。是如何编译、调用、调试的。
在 com.jojo.annotation.processor 建立 SingletonProcessor 类
@SupportedAnnotationTypes("com.jojo.annotation.processor.Singleton") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class SingletonProcessor extends AbstractProcessor { Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); //编译的时候用于输出 } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Singleton.class); set.forEach(element -> { note("hello world"); }); return true ; } //简单封装的函数 private void note(String message){ this.messager.printMessage(Diagnostic.Kind.NOTE, message); } }
在 com.jojo 中建立用来测试的类 SingletonRegistry
@Singleton public class SingletonRegistry { }
编译是个问题,怎么编译呢?编译 SingletonRegistry.java 的时候就要编译器(javac)看到 SingletonProcessor 才能处理啊。。。也就是说编译 SingletonRegistry.java 要 SingletonProcessor 已经编译好了。
先用命令行处理一下
在 maven 项目的根目录下 建立 compile.sh
#!/usr/bin/env bash if [ -d target ]; then rm -rf target; fi mkdir target source=src/main/java/com/jojo javac -cp $JAVA_HOME/lib/tools.jar ${source}/annotation/*.java ${source}/processor/*.java -d target javac -cp target -processor com.jojo.processor.SingletonProcessor ${source}/SingletonRegistry.java -d target
在命令行中执行 sh compile.sh
便可看到输出 Note: hello world
由于真的不知道该如何 debug 。以前一直用上面的方式看输出进行调试。后面在 stackoverflower 上找到一篇文章了发现本身傻逼了
我简单描述一下吧。在 meavn 上添加 google 的 auto-service
依赖
<dependencies> <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.0-rc4</version> </dependency> </dependencies>
这个东东可以在打包成 jar 时候自动生成 META-INF/services/javax.annotation.processing.Processor
文件内容以下
com.jojo.processor.SingletonProcessor
javac 会检测 jar 中 javax.annotation.processing.Processor 文件,并将文件对应的类注册为 processor。编译的时候就会调用到。
在 SingletonProcessor 下添加注解 @AutoService(Processor.class)
@SupportedAnnotationTypes("com.jojo.annotation.processor.Singleton") @SupportedSourceVersion(SourceVersion.RELEASE_8) @AutoService(Processor.class) public class SingletonProcessor extends AbstractProcessor { //.... }
添加后会在 javax.annotation.processing.Processor 文件中写入被注解的类路径。
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
配置参数结果以下
类是以 Test 结尾,并添加上要用的注解
@Singleton public class SingletonRegistryTest { }
mvnDebug clean install
这样会进入 meavn debug 模式。运行后会看到输出
Preparing to execute Maven in debug mode
Listening for transport dt_socket at address: 8000
看到输出 Connected to the target VM, address: 'localhost:8000', transport: 'socket'
也就链接成功,这样会停在你打断点的地方了
回归正传,要生成一个上面所言的单例模式要怎样作呢?
看起来不难,但实际上还真的有点烦的,下面是代码的实现
获取 AST 树要用到 sdk 中的tools.jar,因此要进入依赖
<dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>${java.home}/../lib/tools.jar</systemPath> </dependency>
public class SingletonProcessor extends AbstractProcessor { private Messager messager; private JavacTrees trees; 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); //AST 树 Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); //封装了定义方法、变量、类等等的方法 this.names = Names.instance(context); //用于建立标识符 } //还有更多的函数 }
public class SingletonProcessor extends AbstractProcessor { //...省略上面的 @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //获取被 Singleton 注解标注的类的集合 Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Singleton.class); set.forEach(element -> { //获取到对应的 AST 树 JCTree jcTree = trees.getTree(element); }); return true; } }
我打了个断点,你能够看到 jcTree 的定义是怎样的。我关注的地方是 defs, 如今能够 defs 只定义了一个构造函数、名字也独特叫
这个函数的目的是去掉默认的公有的无参数的构造函数、添加一个私有的无参数构造函数
private void createPrivateConstructor(JCTree.JCClassDecl singletonClass) { JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PRIVATE); JCTree.JCBlock block = treeMaker.Block(0L, nil()); JCTree.JCMethodDecl constructor = treeMaker .MethodDef( modifiers, //修饰符 names.fromString("<init>"), //函数名 null, //方法返回的类型 nil(), //泛型参数 nil(), //参数 nil(), //throw block, //函数代码块,这里是空代码块 null); //默认值 //去掉默认的构造函数 ListBuffer<JCTree> out = new ListBuffer<>(); for (JCTree tree : singletonClass.defs) { if (isPublicDefaultConstructor(tree)) {//是否公有无参数的构造函数 continue; } out.add(tree); } out.add(constructor); singletonClass.defs = out.toList(); } private boolean isPublicDefaultConstructor(JCTree jcTree) { if (jcTree.getKind() == Tree.Kind.METHOD) { JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) jcTree; if (isConstructor(jcMethodDecl) && isNoArgsMethod(jcMethodDecl) && isPublicMethod(jcMethodDecl)) { return true; } } return false; } private static boolean isConstructor(JCTree.JCMethodDecl jcMethodDecl) { String name = jcMethodDecl.name.toString(); if ("<init>".equals(name)) { return true; } return false; } private static boolean isNoArgsMethod(JCTree.JCMethodDecl jcMethodDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = jcMethodDecl.getParameters(); if (jcVariableDeclList == null || jcVariableDeclList.size() == 0) { return true; } return false; } private boolean isPublicMethod(JCTree.JCMethodDecl jcMethodDecl) { JCTree.JCModifiers jcModifiers = jcMethodDecl.getModifiers(); Set<Modifier> modifiers = jcModifiers.getFlags(); if (modifiers.contains(Modifier.PUBLIC)) { return true; } return false; }
在 process 函数中处理构造函数
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Singleton.class); set.forEach(element -> { JCTree jcTree = trees.getTree(element); //修改 jcTree 的方式,能够修改类的定义 jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { createPrivateConstructor(jcClassDecl); } }); }); return true ; }
在终端中输入 mvn clean install
,编译成功后,你能够在 target/test-classes/ 中看到编译后的 SingletonRegistryTest。
javap 反编译看一下 javap -p SingletonRegistryTest.class
Compiled from "SingletonRegistryTest.java" public class SingletonRegistryTest { private SingletonRegistryTest(); }
要达到的结果是这样的
private static class SingletonRegistryHolder { private static SingletonRegistry instance = new SingletonRegistry(); }
private JCTree.JCClassDecl createInnerClass(JCTree.JCClassDecl singletonClass) { JCTree.JCClassDecl innerClass = treeMaker.ClassDef( treeMaker.Modifiers(Flags.PRIVATE | Flags.STATIC), names.fromString(singletonClass.name+"Holder"), //类名 nil(), //泛型参数 null, //extending nil(), //implementing nil() //类定义的详细语句,包括字段,方法定义等 ); addInstanceVar(innerClass, singletonClass); //给类添加添加 instance变量 singletonClass.defs = singletonClass.defs.append(innerClass); return innerClass; } private void addInstanceVar(JCTree.JCClassDecl innerClass, JCTree.JCClassDecl singletonClass) { JCTree.JCIdent singletonClassType = treeMaker.Ident(singletonClass.name); //获取注解的类型 //new SingletonRegistry() 的语句 JCTree.JCNewClass newKeyword = treeMaker.NewClass( null, //encl,enclosingExpression lambda 箭头吗?不太清楚 nil(), //参数类型列表 singletonClassType, //待建立对象的类型 nil(), //参数蕾西 null); //类定义 JCTree.JCModifiers fieldMod = treeMaker.Modifiers(Flags.PRIVATE | Flags.STATIC | Flags.FINAL); //定义变量 JCTree.JCVariableDecl instanceVar = treeMaker.VarDef( fieldMod, //修饰符 names.fromString("instance"), //变量名 singletonClassType, //类型 newKeyword); //赋值语句 innerClass.defs = innerClass.defs.prepend(instanceVar); }
目标完成
public static SingletonRegistry getInstance() { return SingletonRegistryHolder.instance; }
private void createReturnInstance(JCTree.JCClassDecl singletonClass,JCTree.JCClassDecl innerClass){ JCTree.JCModifiers fieldMod = treeMaker.Modifiers(Flags.PUBLIC | Flags.STATIC); JCTree.JCIdent singletonClassType = treeMaker.Ident(singletonClass.name); //获取 return 语句块 JCTree.JCBlock body = addReturnBlock(innerClass); //建立方法 JCTree.JCMethodDecl methodDec = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC|Flags.STATIC), this.names.fromString("getInstance"), singletonClassType, nil(), nil(), nil(), body , null); singletonClass.defs = singletonClass.defs.prepend(methodDec); } private JCTree.JCBlock addReturnBlock(JCTree.JCClassDecl holderInnerClass) { JCTree.JCIdent holderInnerClassType = treeMaker.Ident(holderInnerClass.name); JCTree.JCFieldAccess instanceVarAccess = treeMaker.Select(holderInnerClassType, names.fromString("instance")); //获取内联类中的静态变量 JCTree.JCReturn returnValue = treeMaker.Return(instanceVarAccess);//建立 return 语句 ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(returnValue); return treeMaker.Block(0L, statements.toList()); }
最后的 processor 函数像是这样的
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Singleton.class); set.forEach(element -> { JCTree jcTree = trees.getTree(element); jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { createPrivateConstructor(jcClassDecl); JCTree.JCClassDecl innerClass= createInnerClass(jcClassDecl); createReturnInstance(jcClassDecl,innerClass); } }); }); return true; }
完成了,mvn clean install
一下,使用 idea 查看对应的 .class 文件,生成的结果就好漂亮了。
```java
public class SingletonRegistryTest {
public static SingletonRegistryTest getInstance() {
return SingletonRegistryTest.SingletonRegistryTestHolder.instance;
}
private SingletonRegistryTest() { } private static class SingletonRegistryTestHolder { private static final SingletonRegistryTest instance = new SingletonRegistryTest(); private SingletonRegistryTestHolder() { } }
}