能否创建可替代其他注解的自定义注解?以Lombok@NoArgsConstructor为例
当然可以实现这种替代需求!虽然Java注解本身不支持继承,但我们可以借助编译期注解处理器——尤其是利用Lombok自身的扩展机制——来达成目标。下面我会给出两种可行的方案,你可以根据自己的场景选择:
方案一:基于Lombok扩展(推荐,省力)
这种方法直接复用Lombok已经写好的构造器生成逻辑,只需要让Lombok识别你的自定义注解等同于@NoArgsConstructor(access = AccessLevel.PROTECTED)即可。
步骤1:定义你的自定义注解
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 myConstructor { }
步骤2:编写Lombok注解处理器
我们需要写一个自定义的Handler,让Lombok处理@myConstructor时,生成和@NoArgsConstructor(access=PROTECTED)完全一样的代码:
import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.handlers.JavacNoArgsConstructorHandler; @HandlerPriority(1) // 设置优先级,确保优先处理我们的注解 public class MyConstructorHandler extends JavacAnnotationHandler<myConstructor> { @Override public void handle(AnnotationValues<myConstructor> annotation, myConstructor ast, JavacNode annotationNode) { // 构造一个默认的NoArgsConstructor实例,指定access为PROTECTED NoArgsConstructor proxyAnnotation = new NoArgsConstructor() { @Override public AccessLevel access() { return AccessLevel.PROTECTED; } @Override public Class<? extends java.lang.annotation.Annotation> annotationType() { return NoArgsConstructor.class; } // 其他属性返回Lombok默认值 @Override public String staticName() { return ""; } @Override public boolean force() { return false; } @Override public boolean onConstructor() { return false; } }; // 调用Lombok原生的NoArgsConstructor处理器来生成代码 JavacAnnotationHandler<NoArgsConstructor> handler = new JavacNoArgsConstructorHandler(); handler.handle(AnnotationValues.of(proxyAnnotation), proxyAnnotation, annotationNode); } }
步骤3:注册处理器
在你的项目资源目录下创建META-INF/services/lombok.core.AnnotationHandler文件,添加一行处理器的全类名:
com.yourpackage.lombok.MyConstructorHandler
(注意替换成你实际的包路径)
这样配置后,当你在类上标记@myConstructor时,Lombok就会自动生成一个protected权限的无参构造器,效果和直接使用@NoArgsConstructor(access = AccessLevel.PROTECTED)完全一致。
方案二:自定义独立注解处理器(更灵活)
如果你不想依赖Lombok的扩展机制,也可以自己写一个完整的注解处理器,直接生成构造器代码。这种方式需要处理字节码生成的细节,适合需要高度自定义的场景。
步骤1:定义注解(和方案一相同)
步骤2:编写注解处理器
可以借助JavaPoet库来简化代码生成(需要在项目中引入JavaPoet依赖):
import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import java.util.Set; @SupportedAnnotationTypes("com.yourpackage.myConstructor") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyConstructorProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement element : annotations) { // 遍历所有被@myConstructor标记的类 roundEnv.getElementsAnnotatedWith(element).forEach(classElement -> { // 生成protected无参构造器 MethodSpec constructor = MethodSpec.constructorBuilder() .addModifiers(Modifier.PROTECTED) .build(); // 创建类的TypeSpec,添加构造器 TypeSpec modifiedClass = TypeSpec.classBuilder(classElement.getSimpleName()) .addModifiers(classElement.getModifiers()) .addMethod(constructor) .build(); // 生成Java文件 try { JavaFile.builder(classElement.getEnclosingElement().toString(), modifiedClass) .build() .writeTo(processingEnv.getFiler()); } catch (Exception e) { e.printStackTrace(); } }); } return true; } }
步骤3:注册处理器
同样需要在META-INF/services/javax.annotation.processing.Processor文件中注册你的处理器:
com.yourpackage.MyConstructorProcessor
注意事项
- 两种方案的注解都必须设置
@Retention(RetentionPolicy.SOURCE),因为我们只需要在编译期处理,不需要保留到运行时。 - 方案一需要确保项目正确引入Lombok依赖,并且IDE开启了Lombok的注解处理支持。
- 方案二如果使用JavaPoet,需要添加对应的Maven/Gradle依赖。
内容的提问来源于stack exchange,提问作者user1156544

