###### 注解的定义说明 对于一个注解一般包括以下几个部分: 1. Target:适用目标 有一个注解如下所示: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";
}
可以看到,使用的目标为类上面,如下所示
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200820144803800.png#pic_center)
如果我们尝试使用到方法上,编译器都会报错的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020082014463424.png#pic_center)
报错信息为:```'@Component' not applicable to method```
关于有那些适用范围可以使用,查看类```java.lang.annotation.ElementType```即可。
2. Retention : 保留时机
一般情况下都是```java.lang.annotation.RetentionPolicy#RUNTIME```,也就是说这个注解可以一直保持到JVM中(运行时),而```java.lang.annotation.RetentionPolicy#SOURCE```在编译之后就会被丢弃,而```java.lang.annotation.RetentionPolicy#CLASS```不会保留到运行时。
比如:
```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
}
@SessionScope
@RestController("sessionScopedUserService")
public class SessionScopedUserService extends AbstractUserService {
// ...
}
其中RestController
是@Retention(RetentionPolicy.RUNTIME)
通过java.lang.Class#getDeclaredAnnotations
查找类上的注解,结果如下所示,可以看到SessionScope
是查找不到的,因为它在编译完就被丢弃了,在JVM加载这个类的时候根本就不存在了。
修改执行时机:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
}
再次执行,效果如下所示,可以查询到了。
3. Documented:与javadoc有关,此处不讨论
4. Inherited : 查找的时候是否从父类中查找
比如有以下两个注解
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";
}
其中Qualifier
中包含了@Inherited
元注解。而Component
不包含。假如存在以下的类关系图。SessionScopedUserService
继承自AbstractUserService
,而AbstractUserService
包含以上两个注解。
从SessionScopedUserService
查找以上两个注解如下所示:
从结果可以看出,可以查找到注解Qualifier
,而无法查找到Component
,因为前者包含了元注解@Inherited
,在执行getDeclaredAnnotations
方法的时候会遍历所有的父类去查找对应的注解。
对应的源码java.lang.Class#createAnnotationData
如下所示
if (superClass != null) {
// 查找出父类中的注解
Map<Class<? extends Annotation>, Annotation> superAnnotations =
superClass.annotationData().annotations;
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
// 如果isInherited,则添加到目标annotations当中
if (AnnotationType.getInstance(annotationClass).isInherited()) {
if (annotations == null) { // lazy construction
annotations = new LinkedHashMap<>((Math.max(
declaredAnnotations.size(),
Math.min(12, declaredAnnotations.size() + superAnnotations.size())
) * 4 + 2) / 3
);
}
annotations.put(annotationClass, e.getValue());
}
}
}
注解的基础方法
要想操作注解,首先必须获得类类型(也就是Class
),常用的注解的基础方法如下:
首先getDeclaredAnnotations
和getAnnotation
前面已经使用过。
Class<SessionScopedUserService> aClass = SessionScopedUserService.class;
// 获取该类上的注解
Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();
// 查找特定的注解
Qualifier qualifier = aClass.getAnnotation(Qualifier.class);
Component component = aClass.getAnnotation(Component.class);
通过getAnnotation
方法获取到对应注解类型的实例之后,就可以获取对应的属性值了。比如如下注解定义了三个属性值(value
、scopeName
、proxyMode
)。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
如果要获取对应的属性值,如下所示
Scope scope = aClass.getAnnotation(Scope.class);
String value = scope.value();
String scopeName = scope.scopeName();
ScopedProxyMode scopedProxyMode = scope.proxyMode();
对于以上注解,scopeName既可以认为是属性,同样也可以认为是方法。所以也可以通过如下方式去获取这些值。
// annotationType.getDeclaredMethods() 获取注解上面的方法 一般也被称为属性
Method[] declaredMethods = annotationType.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
// 获取属性方法的参数个数
int parameterCount = declaredMethod.getParameterCount();
// 获取属性名称
String name = declaredMethod.getName();
// 获取属性默认值
Object defaultValue = declaredMethod.getDefaultValue();
// 通过反射获取属性值(此处属性参数个数为0个 因此args为null)
Object invoke = declaredMethod.invoke(declaredAnnotation, null);
}
使用如下所示
package com.example.methodinjection;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
//@SessionScope
//@RestController("sessionScopedUserService")
@Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedUserService extends AbstractUserService {
// ...
}
通过对象直接获取属性值
通过方法获取属性值,首先获取到所有的方法
遍历方法依次获取属性值
元注解与复合注解
所谓的元注解就是作为注解的注解。上面介绍过注解的@Target
,如果这个属性当中包含有ElementType.ANNOTATION_TYPE
,就说明可以作用于注解上面。其实Target本身就是这样一个注解
@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();
}
使用元注解的第一个好处是对一些注解进行分类了。比如在Spring当中
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
这三个注解都包含了元注解@Component
,因此都能作为引入Bean的注解。
By default, classes annotated with @Component, @Repository, @Service, @Controller, or a custom annotation that itself is annotated with @Component are the only detected candidate components.
但是却赋予了不同的语义。@Repository
使用在数据层,而@Service
使用在服务层,@Controller
使用在控制层。因为这三个注解可以认为是继承了@Component
,包含了父亲的逻辑,同时又可以单独扩展自己的逻辑。
Spring provides further stereotype annotations: @Component, @Service, and @Controller.@Component is a generic stereotype for any Spring-managed component. @Repository, @Service,and @Controller are specializations of @Component for more specific use cases, for example,in the persistence, service, and presentation layers, respectively. Therefore, you can annotate your component classes with @Component, but by annotating them with @Repository, @Service, or @Controller instead, your classes are more properly suited for processing by tools or associating
with aspects. For example, these stereotype annotations make ideal targets for pointcuts. It is also possible that @Repository, @Service, and @Controller may carry additional semantics in future releases of the Spring Framework. Thus, if you are choosing between using @Component or @Service for your service layer, @Service is clearly the better choice. Similarly, as stated above, @Repository is already supported as a marker for automatic exception translation in your persistence layer.
Repeatable annotation 可重复注解
如果我们在同一个类上面使用同一个注解,如下图所示:
报错内容如下所示
Duplicate annotation. The declaration of 'org.springframework.context.annotation.ImportResource' does not have a valid java.lang.annotation.Repeatable annotation
意思很明显,重复注解,@ImportResource
不包含@Repeatable
元注解。
这是怎么回事呢?
不妨看另一个注解,
可以看到此时是可以的。
更奇怪的是下面这个结果
通过getDeclaredAnnotations
方法获取到的注解类型为org.springframework.context.annotation.ComponentScans
.这个和org.springframework.context.annotation.ComponentScan
有什么关系么?
源码如下所示
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
ComponentScan[] value();
}
在ComponentScans
注解中包含了ComponentScan
注解数组属性。
再看看ComponentScan
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* Alias for {@link #basePackages}.
* <p>Allows for more concise annotation declarations if no other attributes
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor("basePackages")
String[] value() default {};
.... 省略部分
}
包含元注解信息@Repeatable(ComponentScans.class)
。初次看起来会觉得很奇怪,在ComponentScan
的元注解当中包含ComponentScans
,而在ComponentScans
注解当中包含了ComponentScan
数组。这样的注解使用还蛮多的。比如在mybatis-spring
当中org.mybatis.spring.annotation.MapperScan
和org.mybatis.spring.annotation.MapperScans
也是类似的关系。通过解析注解进行注册相关bean
/**
* A {@link MapperScannerRegistrar} for {@link MapperScans}.
*
* @since 2.0.0
*/
static class RepeatingRegistrar extends MapperScannerRegistrar {
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
}
}
}
}
Spring中解析Annotation的工具类
jdk提供的解析注解的方法只能获取一个类上的注解以及父类上面的可继承注解。而对于注解上的元注解中包含的元注解以及接口上面的注解、继承结构上(父亲、爷爷…)类的注解不可以直接获取。Spring提供了org.springframework.core.annotation.AnnotationUtils
工具类,可以方便处理以上的情况。
首先是针对这个类的说明,用于注解的通用工具方法,可用于处理元注解、桥接方法(编译器为泛型方法生成的替代方法)、父类方法(针对可继承的注解@Inherited)
一般使用AnnotationUtils#findAnnotation(java.lang.reflect.Method, java.lang.Class<A>)
和AnnotationUtils#getAnnotation(java.lang.reflect.Method, java.lang.Class<A>)
来代替JDK提供的方法。这个是针对于获取方法上面的注解。其实AnnotationUtils#getAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>)
和AnnotationUtils#getAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>)
更方便使用。其中java.lang.reflect.AnnotatedElement
既可以指代类,也可以指代方法以及其他包含注解的元素。
比如
Class<SessionScopedUserService> aClass = SessionScopedUserService.class;
// 获取类上面的注解
Component getComponent = AnnotationUtils.getAnnotation(aClass, Component.class);
Component findComponent = AnnotationUtils.findAnnotation(aClass, Component.class);
// 获取方法上的注解
for (Method method : aClass.getMethods()) {
AnnotationUtils.getAnnotation(method, Component.class);
}
其中getAnnotation
和findAnnotation
的区别在于findAnnotation
可以在整个继承结构上查找特定的注解。
比如在上面的例子当中SessionScopedUserService
不包含@Component
注解,而在父类中包含。通过getAnnotation方法是无法获取的,而通过findAnnotation是可以获取的。
getAnnotation
源码如下
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
try {
A annotation = annotatedElement.getAnnotation(annotationType);
if (annotation == null) {
// 遍历元注解查找所有的注解
for (Annotation metaAnn : annotatedElement.getAnnotations()) {
annotation = metaAnn.annotationType().getAnnotation(annotationType);
if (annotation != null) {
break;
}
}
}
return (annotation != null ? synthesizeAnnotation(annotation, annotatedElement) : null);
}
catch (Throwable ex) {
handleIntrospectionFailure(annotatedElement, ex);
return null;
}
}
从源码可以看出,getAnnotation
会遍历所有的元注解依次查找指定注解。不过这已经比jdk提供的方法要强一些了。在SessionScopedUserService
类上面添加注解@Service("service")
.
此时通过getAnnotation
可以查找到@Component
注解(@Component
是@Service
的元注解)
findAnnotation
源码如下:
public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
return findAnnotation(clazz, annotationType, true);
}
缓存结果,如果已经解析过,再次查找,可以直接享受缓存带来的好处
private static <A extends Annotation> A findAnnotation(
Class<?> clazz, @Nullable Class<A> annotationType, boolean synthesize) {
Assert.notNull(clazz, "Class must not be null");
if (annotationType == null) {
return null;
}
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
A result = (A) findAnnotationCache.get(cacheKey);
if (result == null) {
result = findAnnotation(clazz, annotationType, new HashSet<>());
if (result != null && synthesize) {
result = synthesizeAnnotation(result, clazz);
findAnnotationCache.put(cacheKey, result);
}
}
return result;
}
遍历元注解、接口、父类查找特定的注解。
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
try {
A annotation = clazz.getDeclaredAnnotation(annotationType);
if (annotation != null) {
return annotation;
}
// 遍历元注解
for (Annotation declaredAnn : getDeclaredAnnotations(clazz)) {
Class<? extends Annotation> declaredType = declaredAnn.annotationType();
if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) {
annotation = findAnnotation(declaredType, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Throwable ex) {
handleIntrospectionFailure(clazz, ex);
return null;
}
// 遍历接口
for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
// 遍历父类
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || superclass == Object.class) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
从上面的源码不难看出,findAnnotation
要比getAnnotation
要复杂得多,实际使用的时候按需使用。如果简单的使用一次而且不需要遍历父类或结果、元注解的话,直接使用getAnnotation
就可以了。