简介
相信大部分的开发者都用过注解,尤其是对使用过 Spring 的开发者来说,注解是现代 Spring 中不可获取的一部分。Spring 从最开始的 xml 配置到后面的注解配置,不论是从编程习惯还是项目的构建,都对我们程序员产生了非常重要的影响。
除了使用 Spring 自带的注解之外,我们还可以自定义注解。然后通过 AOP 来对注解进行拦截从而处理相应的业务逻辑。
除了 Spring 之外,其实 JDK 本身自带注解,本文将会深入探讨注解的起源和两种不同的使用方式。
更多精彩内容且看:
- 区块链从入门到放弃系列教程 - 涵盖密码学, 超级账本, 以太坊,Libra, 比特币等持续更新
- Spring Boot 2.X 系列教程: 七天从无到有掌握 Spring Boot- 持续更新
- Spring 5.X 系列教程: 满足你对 Spring5 的一切想象 - 持续更新
- java 程序员从小工到专家成神之路(2020 版)- 持续更新中, 附详细文章教程
更多内容请访问 www.flydean.com
注解的起源和 marker interfaces
先看一个最简单的注解:
@CustUserAnnotation
public class CustUser {}
上面我们将 CustUser 标记为一个自定义的注解 @CustUserAnnotation。
注解其实是在 JDK 5 中引入的。那么在 JDK 5 之前,注解是用什么方式来表示的呢?答案就是 marker interfaces。
marker interfaces 中文翻译叫做标记接口,标记接口就是说这个接口使用来做标记用的,内部并没有提供任何方法或者字段。
在 java 中有很多标记接口,最常见的就是 Cloneable,Serializable,还有 java.util 包中的 EventListener 和 RandomAccess。
以 Cloneable 为例:
/*
* @since 1.0
*/
public interface Cloneable {}
该接口从 java1.0 就开始有了。实现该接口的类才能够调用 Object 中的 clone 方法。
我们在代码中如何判断类是否实现了 Cloneable 接口呢?
public Object clone() throws CloneNotSupportedException {if (this instanceof Cloneable) {return super.clone();
} else {throw new CloneNotSupportedException();
}
}
很简单,通过 instanceof 来判断是否是 Cloneable 即可。
marker interfaces 好用是好用,但是有一些缺点,比如没有额外的元数据信息,功能太过单一,并且会和正常的 interface 混淆。实现起来也比一般的 interface 复杂。
正式由于这些原因,在 JDK5 中,引入了注解 Annotation。
注解的定义
注解是由 @interface 来定义的。创建一个 annotation 需要指定其 target 和 retention,并可以自定义参数。
我们举个例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustUserAnnotation {int value();
String name();
String[] addresses();
}
上面是我自定义的一个注解。
Retention
Retention 表示注解将会在什么阶段可见。它有三个可选值:
SOURCE 表示只在源代码可见,编译的时候就会被丢弃。
CLASS 表示在 class 可见,也就是说编译的时候可见,但是运行时候不可见。
RUNTIME 表示运行时候可见。什么时候才需要运行时可见呢?那就是使用到反射的时候。我们会在后面的例子中具体的描述这种情况。
Retention 本身也是一个注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();}
Target
Target 表示这个注解将会用到什么地方。它有 12 个值。
TYPE 表示用在 Class,interface,enum 或者 record 上。
FIELD 表示用在 class 的字段上。
METHOD 表示用在方法上。
PARAMETER 表示用在方法上面。
CONSTRUCTOR 用在构造函数上。
LOCAL_VARIABLE 用在本地变量上。
ANNOTATION_TYPE 用在注解上。
PACKAGE 用在 package 上。
TYPE_PARAMETER 用在类型参数上。
TYPE_USE 用在任何 TYPE 使用上。
TYPE_PARAMETER 和 TYPE_USE 有什么区别呢?
TYPE_USE 用在任何类型的使用上面,比如申明,泛型,转换:
@Encrypted String data
List<@NonNull String> strings
MyGraph = (@Immutable Graph) tmpGraph;
而 TYPE_PARAMETER 用在类型参数上:
class MyClass<T> {...}
MODULE 用在 module 上。
RECORD_COMPONENT 预览功能,和 records 相关。
Target 和 Retention 一样也是一个注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
自定义参数
注解也可以自定参数,参数可以是下的类型:
- 基础类型:int,long,double 等
- String
- Class<T>
- 枚举类型
- 其他的注解类型
- 上面 5 中的数组
上面我们的自定义类型定义了三个参数:
int value();
String name();
String[] addresses();
我们看下怎么使用:
@CustUserAnnotation(value = 100, name="jack ma",addresses = {"人民路","江西路"})
public class CustUser {}
在使用中,我们需要传入自定义的参数,当然你也可以使用 default 在注解中提供默认值,这样就不需要从外部传入。
在运行时使用注解
在运行时,我们可以使用反射的 API 来获得注解,并获取注解中的自定义变量,从而进行相应的业务逻辑处理。
CustUser custUser= new CustUser();
Annotation[] annotations= custUser.getClass().getAnnotations();
Stream.of(annotations).filter(annotation -> annotation instanceof CustUserAnnotation)
.forEach(annotation -> log.info(((CustUserAnnotation) annotation).name()));
还是刚才的例子,我们通过 getAnnotations 方法获取到注解的值。
在运行时是用注解当然是个不错的主意,但是反射用的太多的话其实会影响程序的性能。
那么我们可以不可以将运行时的注解提前到编译时呢?答案是肯定的。
在编译时使用注解
要想在编译时使用注解,就要介绍今天我们的最后一部分内容 annotation processors。
自定义 processors 需要实现 javax.annotation.processing.Processor 接口。
接下来我们自定义一个 Processor:
@SupportedAnnotationTypes("com.flydean.*")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {System.out.println("process annotation!");
annotations.forEach(annotation -> {Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
elements.stream()
.filter(TypeElement.class::isInstance)
.map(TypeElement.class::cast)
.map(TypeElement::getQualifiedName)
.map(name -> "Class" + name + "is annotated with" + annotation.getQualifiedName())
.forEach(System.out::println);
});
return true;
}
}
SupportedAnnotationTypes 表示支持的注解类型。
SupportedSourceVersion 表示支持的源代码版本。
最后我们在 process 方法中,获取了注解类的一些信息。
有了 processor 我们怎么在 maven 环境中使用呢?
最简单的办法就是在 maven 的 maven-compiler-plugin 插件中添加 annotationProcessors,如下所示:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>14</source>
<target>14</target>
<annotationProcessors>
<annotationProcessor>com.flydean.MyProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
如果不添加,默认情况下编译器会从 classpath 中去寻找 META-INF/services/javax.annotation.processing.Processor 文件,这个文件里面列出了对外提供的注解处理器。编译器会加载这些注解处理器去处理当前项目的注解。
lombok 应该大家都用过吧,它实际上为我们提供了两个注解处理器:
很不幸的是,因为我在 CustUser 中使用了 lombok 中的 log,如果像上面一样显示指定 annotationProcessor 则会将覆盖默认的查找路径,最后会导致 lombok 失效。
那应该怎么处理才能兼容 lombok 和自定义的 processor 呢?
我们可以把自定义 processor 单独成一个模块,也做成 lombok 这样的形式:
这个 processor 的模块编译参数需要加上一个 proc none 的参数:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>14</source>
<target>14</target>
<proc>none</proc>
</configuration>
</plugin>
</plugins>
</build>
proc 是设置是否需要在本项目中启用 processor。对于 processor 项目来说,它本身还没有编译,如果启用就会出现找不到类的错误。所以这里我们需要将 proc 设置为 none。
最后我们的 annotation-usage 项目可以不需要 annotationProcessors 的配置就可以自动从 classpath 中读取到自定义的 processor 了。
总结
本文介绍了 marker interface,annotation 和 annotation processor,并详细讲解了如何在 maven 程序中使用他们。
本文的例子 [https://github.com/ddean2009/
learn-java-base-9-to-20](https://github.com/ddean2009/…
本文作者:flydean 程序那些事
本文链接:http://www.flydean.com/marker-interface-annotation-processor/
本文来源:flydean 的博客
欢迎关注我的公众号: 程序那些事,更多精彩等着您!