Java自定义Annotation注解开发详解
介绍
一、运行期的自定义注解
1. Class Level Annotation
2. Method Level Annotation
3. Field Level Annotation
4. 使用自定义注解
5. 处理自定义注解的逻辑
二、编译期的自定义注解
1. 创建自定义注解
2. 实现一个Processor
3. 注册你的Processor
4. 测试你的自定义注解
Java中的注解是每个开发都会遇到的,但是如果要自定义自己的注解,则需要遵循一些基本的步骤,一般注解的开发有2个基本方法:
下面将通过2个例子来说明开发一个自定义注解需要哪些步骤。首先我们将看到一个非常简单的例子,我们用这个例子来说明开发自定义annotation的一些基本步骤,我们的第二个例子将介绍自定义注解以及Annotation Processor的一些用法
在下面的例子中,我们将创建3种不同类型自定义注解,以收集所有有自定义注解的类和方法
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface ClassAnnotation { public String alias() default "";}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MethodAnnotation { public String alias() default "";}
@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类型的参数
@ClassAnnotation(alias = "test")public class Test { @FieldAnnotation private String name; @MethodAnnotation(alias="debug") public String getName() { return name; }}
我们在class, field, method上分别运用我们的自定义注解,并且在method上开启debug日志
我们已经介绍了如何定义自己的注解,以及如何使用我们的注解,接下来我们将用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是代码级别的注解处理器,所以它一般在编译期帮助我们生成我们想要的动态代码,配置文件,文档等,它的使用场景相当广泛,一般包含以下几种,
在这个例子中,我们将创建一个Immutable的类级别的注解,该注解将在编译期检查class中所有的field是否有final关键字修饰
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface Immutable {}
Immutable
注解@Target(ElementType.TYPE)
表示Immutable
注解只能放在类上@Retention(RetentionPolicy.SOURCE)
表示Immutable
注解只在编译期生效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关键字,如果没有记录该方法,最后抛出异常Java实际上提供了好几种选择来帮助我们注册自己的Processor使我们的自定义注解生效,这里我们只介绍最常用的方法来注册Processor
@AutoService(Processor.class)public class ImmutableProcessor extends AbstractProcessor { // ...}
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>
@Immutablepublic class Test { public String name;}
在编译期,会收到 Class Test is not @Immutable, fields name are not declared as final 的报错。至此一个简单的使用annotation processor的例子已经完成。
后续文章会继续深入分析介绍Java自定义注解在各个framework中的使用。
https://blog.csdn.net/xchann/article/details/126374273