关于Annotation的那些事儿

33 篇文章 3 订阅


###### 注解的定义说明 对于一个注解一般包括以下几个部分: 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),常用的注解的基础方法如下:
首先getDeclaredAnnotationsgetAnnotation前面已经使用过。

Class<SessionScopedUserService> aClass = SessionScopedUserService.class;
// 获取该类上的注解
Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();
// 查找特定的注解
Qualifier qualifier = aClass.getAnnotation(Qualifier.class);
Component component = aClass.getAnnotation(Component.class);

通过getAnnotation方法获取到对应注解类型的实例之后,就可以获取对应的属性值了。比如如下注解定义了三个属性值(valuescopeNameproxyMode)。

@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 &mdash; 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.MapperScanorg.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);
}

其中getAnnotationfindAnnotation的区别在于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就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值