Java自定义Annotation注解开发详解
目录
介绍
Java中的注解是每个开发都会遇到的,但是如果要自定义自己的注解,则需要遵循一些基本的步骤,一般注解的开发有2个基本方法:
- 在运行期,通过反射获得当前类,方法,变量上的注解信息来实现自定义注解的功能
- 在编译期,通过Annotation Processer预编译生成想要的任何内容或者逻辑
下面将通过2个例子来说明开发一个自定义注解需要哪些步骤。首先我们将看到一个非常简单的例子,我们用这个例子来说明开发自定义annotation的一些基本步骤,我们的第二个例子将介绍自定义注解以及Annotation Processor的一些用法
一、运行期的自定义注解
在下面的例子中,我们将创建3种不同类型自定义注解,以收集所有有自定义注解的类和方法
1. Class Level Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
public String alias() default "";
}
2. Method Level Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
public String alias() default "";
}
3. Field Level Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
public String alias() default "";
}
@Retention
注解解释:
@Retention(RetentionPolicy.SOURCE)
: 该注解只在编译期生效,生成的class文件并不包含该注解@Retention(RetentionPolicy.CLASS)
: 该注解会被保留在class文件中,但是运行期不会生效@Retention(RetentionPolicy.RUNTIME)
: 该注解会被保留在class文件中,并且会在运行期生效
@Target
注解解释如下,这里只列举了部分@Target
类型,更多的类型请参看JavaDoc。
@Target(ElementType.TYPE)
: 该注解只能运用到Class, Interface, enum上@Target(ElementType.FIELD)
: 该注解只能运用到Field上@Target(ElementType.METHOD)
: 该注解只能运用到方法上
注解中还有一个alias
的string类型参数,缺省值是空字符串,在下一节我们将看到如何使用这个string类型的参数
4. 使用自定义注解
@ClassAnnotation(alias = "test")
public class Test {
@FieldAnnotation
private String name;
@MethodAnnotation(alias="debug")
public String getName() {
return name;
}
}
我们在class, field, method上分别运用我们的自定义注解,并且在method上开启debug日志
5. 处理自定义注解的逻辑
我们已经介绍了如何定义自己的注解,以及如何使用我们的注解,接下来我们将用Java的Reflection API来实现我们自定义注解的逻辑
public void gatherAnnotations {
Map<String, Class> classMap = new HashMap<>();
Class<Test> obj = Test.class;
if(obj.isAnnotationPresent(ClassAnnotation.class)) {
ClassAnnotation classAnnotation = obj.getAnnotation(ClassAnnotation.class);
if ("".equals(classAnnotation.alias())) {
classMap.put(obj.getName(), obj);
}else{
classMap.put(classAnnotation.alias(), obj);
}
}
Map<String, Method> methodMap = new HashMap<>();
for (Method method : obj.getDeclaredMethods()) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
if ("".equals(methodAnnotation.alias())) {
methodMap.put(method.getName(), method);
}else{
methodMap.put(methodAnnotation.alias(), method);
}
}
}
}
这里我们运用Java反射收集了所有有我们自定义注解的类和方法,并放到相应的Map中。
至此,我们完成了一个简单的运行期自定义注解的例子,这个例子看上去没有实际的用处,但是在真正的业务场景中,有很多应用都是基于此类逻辑,例如Spring中的@Service和@Autowired注解大都基于这样的逻辑,来进行后续的初始化和注入。
二、编译期的自定义注解
Annotation Processor是代码级别的注解处理器,所以它一般在编译期帮助我们生成我们想要的动态代码,配置文件,文档等,它的使用场景相当广泛,一般包含以下几种,
- 生成代码或者properties文件
- 修改源文件,例如为Pojo生成getter和setter方法
- 一些源文件分析诊断的案例,本文就属于这一类
在这个例子中,我们将创建一个Immutable的类级别的注解,该注解将在编译期检查class中所有的field是否有final关键字修饰
1. 创建自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Immutable {
}
- 定义了一个
Immutable
注解 @Target(ElementType.TYPE)
表示Immutable
注解只能放在类上@Retention(RetentionPolicy.SOURCE)
表示Immutable
注解只在编译期生效
2. 实现一个Processor
JDK中已经为我们实现了一个AbstractProcessor
, 所以我们要做的是扩展这个Abstract类,并且实现里面的process
方法
@SupportedAnnotationTypes("annotation.Immutable")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class ImmutableProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
List<String> nonPublicFields = new LinkedList<>();
for ( Element element : roundEnv.getElementsAnnotatedWith(Immutable.class)) {
if( element instanceof TypeElement ) {
TypeElement typeElement = (TypeElement) element;
for( final Element enclosedElement: typeElement.getEnclosedElements() ) {
if( enclosedElement instanceof VariableElement) {
VariableElement variableElement = ( VariableElement )enclosedElement;
if( !variableElement.getModifiers().contains( Modifier.FINAL ) ) {
nonPublicFields.add(variableElement.getSimpleName().toString());
}
}
}
if (nonPublicFields.size() > 0) {
processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR,
String.format( "Class %s is not @Immutable, fields %s are not declared as final",
typeElement.getSimpleName(), String.join(",", nonPublicFields)
)
);
}
}
}
return true;
}
}
@SupportedAnnotationTypes("annotation.Immutable")
表示ImmutableProcessor
只用于annotation.Immutable
注解@SupportedSourceVersion(SourceVersion.RELEASE_11)
表示这个processor支持的JDK最低版本是11@AutoService(Processor.class)
表示使用Google auto-service library注册这个processor,下一节将讨论如何注册你的processorprocess
方法轮训找到所有有Immutable
注解的类,然后遍历所有的方法,查找是否有final关键字,如果没有记录该方法,最后抛出异常
3. 注册你的Processor
Java实际上提供了好几种选择来帮助我们注册自己的Processor使我们的自定义注解生效,这里我们只介绍最常用的方法来注册Processor
- 通过Google auto-service library来注册你的processor
@AutoService(Processor.class)
public class ImmutableProcessor extends AbstractProcessor {
// ...
}
- 首先你需要引入auto-service library
- 在你的processor上加上@AutoService(Processor.class)注解
- 编译后,你将在Jar包的META-INF/services下看到javax.annotation.processing.Processor文件,文件里包含了你的processor
2. 通过Maven plugin来注册你的processor
使用maven前你的processor必须已经编译,通过其他jar文件的形式加到了dependencies里
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>
annotation.ImmutableProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
4. 测试你的自定义注解
@Immutable
public class Test {
public String name;
}
在编译期,会收到 Class Test is not @Immutable, fields name are not declared as final 的报错。至此一个简单的使用annotation processor的例子已经完成。
后续文章会继续深入分析介绍Java自定义注解在各个framework中的使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)