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中的使用。