1. 概述
在本教程中,我们将使用 Lombok 实现一个自定义注解,用于简化单例模式(Singleton)的实现,避免写一堆模板代码。
Lombok 是一个强大的 Java 库,用于减少样板代码。如果你还不熟悉它,可以先看看 Lombok 特性入门。
⚠️ 重要提示:Lombok 1.14.8 是我们能用于本教程的最后一个兼容版本。从 1.16.0 开始,Lombok 隐藏了其内部 API,导致无法像本文这样扩展自定义注解。
2. Lombok 作为注解处理器
Java 允许开发者在编译期处理注解,并基于注解生成新的源文件。因此像 Hibernate 这样的库可以大量使用注解来减少样板代码。
注解处理机制详见 Java 注解处理教程。
Project Lombok 本质上也是一个注解处理器。它会将注解委托给特定的处理器进行处理。
在委托过程中,Lombok 会将被注解代码的 AST(抽象语法树)传递给处理器,从而允许处理器通过扩展 AST 来修改代码。
3. 实现自定义注解
3.1. 扩展 Lombok
说实话,扩展 Lombok 并添加自定义注解并不容易。
因为 从较新版本开始,Lombok 使用 Shadow ClassLoader(SCL)将 .class
文件打包为 .scl
文件,隐藏了内部类。这迫使我们只能 fork Lombok 源码并在其中实现自定义注解。
不过好消息是,Lombok 提供了丰富的工具类,简化了自定义处理器和 AST 修改的工作。
3.2. Singleton 注解
实现一个单例类通常需要大量样板代码。对于没有使用依赖注入框架的应用来说,这些代码就是纯粹的重复劳动。
比如,一个典型的单例实现如下:
public class SingletonRegistry {
private SingletonRegistry() {}
private static class SingletonRegistryHolder {
private static SingletonRegistry registry = new SingletonRegistry();
}
public static SingletonRegistry getInstance() {
return SingletonRegistryHolder.registry;
}
// 其他方法
}
而使用自定义注解后,只需要这样写:
@Singleton
public class SingletonRegistry {}
对应的 Singleton
注解定义如下:
@Target(ElementType.TYPE)
public @interface Singleton {}
重点来了:Lombok 的 Singleton 处理器会通过修改 AST,自动生成我们上面看到的完整实现代码。
由于每个编译器的 AST 不同,我们需要为每个编译器实现对应的处理器。Lombok 支持 javac
(Maven/Gradle 和 NetBeans 使用)和 Eclipse 编译器。
接下来我们将分别实现这两个编译器的处理器。
4. 实现 javac
的处理器
4.1. Maven 依赖
首先引入 Lombok 依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
</dependency>
此外,我们还需要 tools.jar
来访问和修改 javac
的 AST。但该包没有 Maven 仓库,最简单的做法是通过 Profile 引入:
<profiles>
<profile>
<id>default-tools.jar</id>
<activation>
<property>
<name>java.vendor</name>
<value>Oracle Corporation</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>${java.version}</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</profile>
</profiles>
4.2. 继承 JavacAnnotationHandler
要实现自定义处理器,需要继承 Lombok 的 JavacAnnotationHandler
:
public class SingletonJavacHandler extends JavacAnnotationHandler<Singleton> {
public void handle(
AnnotationValues<Singleton> annotation,
JCTree.JCAnnotation ast,
JavacNode annotationNode) {}
}
然后实现 handle()
方法。Lombok 会将注解的 AST 作为参数传入。
4.3. 修改 AST
这里是最复杂的部分。修改现有的 AST 并不简单。
不过幸运的是,Lombok 提供了 JavacHandlerUtil
和 JavacTreeMaker
等工具类,用于生成代码并注入到 AST 中。
我们使用这些工具类来生成 SingletonRegistry
的代码:
public void handle(
AnnotationValues<Singleton> annotation,
JCTree.JCAnnotation ast,
JavacNode annotationNode) {
Context context = annotationNode.getContext();
Javac8BasedLombokOptions options = Javac8BasedLombokOptions
.replaceWithDelombokOptions(context);
options.deleteLombokAnnotations();
JavacHandlerUtil
.deleteAnnotationIfNeccessary(annotationNode, Singleton.class);
JavacHandlerUtil
.deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
JavacNode singletonClass = annotationNode.up();
JavacTreeMaker singletonClassTreeMaker = singletonClass.getTreeMaker();
addPrivateConstructor(singletonClass, singletonClassTreeMaker);
JavacNode holderInnerClass = addInnerClass(singletonClass, singletonClassTreeMaker);
addInstanceVar(singletonClass, singletonClassTreeMaker, holderInnerClass);
addFactoryMethod(singletonClass, singletonClassTreeMaker, holderInnerClass);
}
注意:**deleteAnnotationIfNeccessary()
和 deleteImportFromCompilationUnit()
是 Lombok 提供的工具方法,用于移除注解和相关导入语句。**
接下来看具体方法的实现。
添加私有构造函数:
private void addPrivateConstructor(
JavacNode singletonClass,
JavacTreeMaker singletonTM) {
JCTree.JCModifiers modifiers = singletonTM.Modifiers(Flags.PRIVATE);
JCTree.JCBlock block = singletonTM.Block(0L, nil());
JCTree.JCMethodDecl constructor = singletonTM
.MethodDef(
modifiers,
singletonClass.toName("<init>"),
null, nil(), nil(), nil(), block, null);
JavacHandlerUtil.injectMethod(singletonClass, constructor);
}
添加内部静态类 SingletonHolder
:
private JavacNode addInnerClass(
JavacNode singletonClass,
JavacTreeMaker singletonTM) {
JCTree.JCModifiers modifiers = singletonTM
.Modifiers(Flags.PRIVATE | Flags.STATIC);
String innerClassName = singletonClass.getName() + "Holder";
JCTree.JCClassDecl innerClassDecl = singletonTM
.ClassDef(modifiers, singletonClass.toName(innerClassName),
nil(), null, nil(), nil());
return JavacHandlerUtil.injectType(singletonClass, innerClassDecl);
}
添加实例变量:
private void addInstanceVar(
JavacNode singletonClass,
JavacTreeMaker singletonClassTM,
JavacNode holderClass) {
JCTree.JCModifiers fieldMod = singletonClassTM
.Modifiers(Flags.PRIVATE | Flags.STATIC | Flags.FINAL);
JCTree.JCClassDecl singletonClassDecl
= (JCTree.JCClassDecl) singletonClass.get();
JCTree.JCIdent singletonClassType
= singletonClassTM.Ident(singletonClassDecl.name);
JCTree.JCNewClass newKeyword = singletonClassTM
.NewClass(null, nil(), singletonClassType, nil(), null);
JCTree.JCVariableDecl instanceVar = singletonClassTM
.VarDef(
fieldMod,
singletonClass.toName("INSTANCE"),
singletonClassType,
newKeyword);
JavacHandlerUtil.injectField(holderClass, instanceVar);
}
添加工厂方法:
private void addFactoryMethod(
JavacNode singletonClass,
JavacTreeMaker singletonClassTreeMaker,
JavacNode holderInnerClass) {
JCTree.JCModifiers modifiers = singletonClassTreeMaker
.Modifiers(Flags.PUBLIC | Flags.STATIC);
JCTree.JCClassDecl singletonClassDecl
= (JCTree.JCClassDecl) singletonClass.get();
JCTree.JCIdent singletonClassType
= singletonClassTreeMaker.Ident(singletonClassDecl.name);
JCTree.JCBlock block
= addReturnBlock(singletonClassTreeMaker, holderInnerClass);
JCTree.JCMethodDecl factoryMethod = singletonClassTreeMaker
.MethodDef(
modifiers,
singletonClass.toName("getInstance"),
singletonClassType, nil(), nil(), nil(), block, null);
JavacHandlerUtil.injectMethod(singletonClass, factoryMethod);
}
工厂方法返回 INSTANCE
,实现如下:
private JCTree.JCBlock addReturnBlock(
JavacTreeMaker singletonClassTreeMaker,
JavacNode holderInnerClass) {
JCTree.JCClassDecl holderInnerClassDecl
= (JCTree.JCClassDecl) holderInnerClass.get();
JavacTreeMaker holderInnerClassTreeMaker
= holderInnerClass.getTreeMaker();
JCTree.JCIdent holderInnerClassType
= holderInnerClassTreeMaker.Ident(holderInnerClassDecl.name);
JCTree.JCFieldAccess instanceVarAccess = holderInnerClassTreeMaker
.Select(holderInnerClassType, holderInnerClass.toName("INSTANCE"));
JCTree.JCReturn returnValue = singletonClassTreeMaker
.Return(instanceVarAccess);
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(returnValue);
return singletonClassTreeMaker.Block(0L, statements.toList());
}
✅ 至此,我们已经完成了对 javac
编译器的 AST 修改。
4.4. 通过 SPI 注册处理器
到目前为止,我们只是实现了处理器,还需要通过 SPI 注册。
通常,注解处理器通过 META-INF/services
被发现。Lombok 也是这样维护处理器列表的,并使用 SPI 框架自动更新。
我们使用 metainf-services 来简化注册:
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.8</version>
</dependency>
然后在处理器上加上注解:
@MetaInfServices(JavacAnnotationHandler.class)
public class SingletonJavacHandler extends JavacAnnotationHandler<Singleton> {}
✅ 编译时会自动生成 lombok.javac.JavacAnnotationHandler
文件。
5. 实现 Eclipse IDE 的处理器
5.1. Maven 依赖
类似 tools.jar
,我们需要引入 Eclipse 的 JDT 库来操作 AST:
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>core</artifactId>
<version>3.3.0-v_771</version>
</dependency>
5.2. 继承 EclipseAnnotationHandler
实现 Eclipse 的处理器:
@MetaInfServices(EclipseAnnotationHandler.class)
public class SingletonEclipseHandler
extends EclipseAnnotationHandler<Singleton> {
public void handle(
AnnotationValues<Singleton> annotation,
Annotation ast,
EclipseNode annotationNode) {}
}
通过 SPI 注解 @MetaInfServices
,这个处理器会在 Eclipse 编译时生效。
5.3. 修改 AST
注册完成后,开始修改 Eclipse 的 AST:
public void handle(
AnnotationValues<Singleton> annotation,
Annotation ast,
EclipseNode annotationNode) {
EclipseHandlerUtil
.unboxAndRemoveAnnotationParameter(
ast,
"onType",
"@Singleton(onType=", annotationNode);
EclipseNode singletonClass = annotationNode.up();
TypeDeclaration singletonClassType
= (TypeDeclaration) singletonClass.get();
ConstructorDeclaration constructor
= addConstructor(singletonClass, singletonClassType);
TypeReference singletonTypeRef
= EclipseHandlerUtil.cloneSelfType(singletonClass, singletonClassType);
StringBuilder sb = new StringBuilder();
sb.append(singletonClass.getName());
sb.append("Holder");
String innerClassName = sb.toString();
TypeDeclaration innerClass
= new TypeDeclaration(singletonClassType.compilationResult);
innerClass.modifiers = AccPrivate | AccStatic;
innerClass.name = innerClassName.toCharArray();
FieldDeclaration instanceVar = addInstanceVar(
constructor,
singletonTypeRef,
innerClass);
FieldDeclaration[] declarations = new FieldDeclaration[]{instanceVar};
innerClass.fields = declarations;
EclipseHandlerUtil.injectType(singletonClass, innerClass);
addFactoryMethod(
singletonClass,
singletonClassType,
singletonTypeRef,
innerClass,
instanceVar);
}
添加私有构造函数:
private ConstructorDeclaration addConstructor(
EclipseNode singletonClass,
TypeDeclaration astNode) {
ConstructorDeclaration constructor
= new ConstructorDeclaration(astNode.compilationResult);
constructor.modifiers = AccPrivate;
constructor.selector = astNode.name;
EclipseHandlerUtil.injectMethod(singletonClass, constructor);
return constructor;
}
添加实例变量:
private FieldDeclaration addInstanceVar(
ConstructorDeclaration constructor,
TypeReference typeReference,
TypeDeclaration innerClass) {
FieldDeclaration field = new FieldDeclaration();
field.modifiers = AccPrivate | AccStatic | AccFinal;
field.name = "INSTANCE".toCharArray();
field.type = typeReference;
AllocationExpression exp = new AllocationExpression();
exp.type = typeReference;
exp.binding = constructor.binding;
field.initialization = exp;
return field;
}
添加工厂方法:
private void addFactoryMethod(
EclipseNode singletonClass,
TypeDeclaration astNode,
TypeReference typeReference,
TypeDeclaration innerClass,
FieldDeclaration field) {
MethodDeclaration factoryMethod
= new MethodDeclaration(astNode.compilationResult);
factoryMethod.modifiers
= AccStatic | ClassFileConstants.AccPublic;
factoryMethod.returnType = typeReference;
factoryMethod.sourceStart = astNode.sourceStart;
factoryMethod.sourceEnd = astNode.sourceEnd;
factoryMethod.selector = "getInstance".toCharArray();
factoryMethod.bits = ECLIPSE_DO_NOT_TOUCH_FLAG;
long pS = factoryMethod.sourceStart;
long pE = factoryMethod.sourceEnd;
long p = (long) pS << 32 | pE;
FieldReference ref = new FieldReference(field.name, p);
ref.receiver = new SingleNameReference(innerClass.name, p);
ReturnStatement statement
= new ReturnStatement(ref, astNode.sourceStart, astNode.sourceEnd);
factoryMethod.statements = new Statement[]{statement};
EclipseHandlerUtil.injectMethod(singletonClass, factoryMethod);
}
⚠️ 最后,需要将处理器加入 Eclipse 的 boot classpath。通常通过修改 eclipse.ini
实现:
-Xbootclasspath/a:singleton-1.0-SNAPSHOT.jar
6. 在 IntelliJ 中使用自定义注解
不同编译器需要不同的 Lombok 处理器,比如我们已经实现的 javac
和 Eclipse。
但 IntelliJ 并不支持 Lombok 自定义处理器。它通过 插件 提供 Lombok 支持。
因此,❌ 任何新的注解都需要插件显式支持,包括 Lombok 官方新增的注解也一样。
7. 总结
本文我们实现了基于 Lombok 的自定义注解,并在不同编译器中修改 AST 来生成单例代码。
完整代码可在 GitHub 获取。