开发过程中,我们经常通过在类上打注解的方式来给它做标记,以便后期对其进行相应操作。
比如Springboot在启动的时候,会对标有@Component注解的类进行加载和初始化。
那么,如何判断某个类是否有某个注解呢?
很多人会说,这还不简单,不就是利用反射检索该类的注解集合里有没有这个注解不就好了吗。
但这样是不全面的,因为类可能继承自某个父类或者接口,而其父类或者接口上存在该注解。
同时,该注解可能是个复合注解,即该注解也是被其他注解所注解的。
所以,判断某个类是否有某个注解的完善流程应该包含4个方面:
- 检索该类是否直接被该注解标记;
- 递归检索该类注解的注解中是否存在目标注解;
- 递归检索该类的接口中是否存在目标注解;
- 递归检索该类的父类是否存在目标注解。
Spring框架的AnnotationUtils.findAnnotation方法就是基于上述流程来实现的。
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 : clazz.getDeclaredAnnotations()) {
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();
// 如果父类不存在或者父类为Object,则直接返回null
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
同时,AnnotationUtils会通过缓存来加快检索速度。
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) {
// 否则执行注解检索,即上面讲过的4个过程
result = findAnnotation(clazz, annotationType, new HashSet<>());
if (result != null && synthesize) {
result = synthesizeAnnotation(result, clazz);
// 将结果更新到缓存中
findAnnotationCache.put(cacheKey, result);
}
}
return result;
}
缓存是使用ConcurrentReferenceHashMap来作为容器的,其和ConcurrentHashMap不同的是,ConcurrentReferenceHashMap可以声明容器对象的引用强度,默认使用的是软连接,即当内存不足时,会被GC强制回收,避免OOM。
可以看出,仅仅判断某个类是否有某个注解这样1个简单的需求,我们想要做的完善,代码写的健壮,也不是一件简单的事情。