文章目录
- JAVA 注解搜索工具类与注解原理讲解(获取方法和类上所有的某个注解,父类继承的注解也支持获取)
- 代码
- 测试
- 方法上加注解,类上不加
- 类上加注解、方法上加注解
- 注解原理
- 性能测试
JAVA 注解搜索工具类与注解原理讲解(获取方法和类上所有的某个注解,父类继承的注解也支持获取)
基于Spring的AnnotatedElementUtils工具,支持从当前类、父类、接口搜索(支持限制最大深度),支持传入搜索配置、启用缓存。
代码
import cn.hutool.core.collection.CollectionUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.core.annotation.AnnotatedElementUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author chenfuxing
* date: 2024/6/19
* description: 注解工具类
**/
public class AnnotationUtil extends AnnotatedElementUtils {
/**
* 默认注解搜索配置
*/
private static final AnnotationSearchConfig DEFAULT_SEARCH_CONFIG = new AnnotationSearchConfig();
/**
* 缓存
*/
private static final Map<String, Set<? extends Annotation>> CACHE = new ConcurrentHashMap<>();
/**
* 注解搜索配置
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@ToString
public static class AnnotationSearchConfig {
/**
* 从类上搜索注解
*/
private boolean searchFromClass = true;
/**
* 从父类上搜索注解
*/
private boolean searchSuperClass = true;
/**
* 从父类上搜索注解的最大深度
*/
private int searchSuperMaxDepth = 3;
/**
* 从类的接口上搜索注解
*/
private boolean searchFromInterfaces = true;
/**
* 允许使用缓存
*/
private boolean enableCache = true;
}
/**
* 获取缓存key
*
* @param element
* @param annotationType
* @param config
* @return
*/
public static String getCacheKey(AnnotatedElement element, Class<?> annotationType, AnnotationSearchConfig config) {
StringBuilder sb = new StringBuilder();
sb.append(element != null ? element.toString() : "null");
sb.append("-");
sb.append(annotationType != null ? annotationType.getName() : "null");
sb.append("-");
sb.append(config != null ? config.toString() : "null");
return sb.toString();
}
/**
* 获取注解
* 若有多个则只会返回第一个注解(方法上的优先于类上的)
*
* @param element
* @param annotationType
* @param <A>
* @return
*/
public static <A extends Annotation> A getAnnotation(AnnotatedElement element, Class<A> annotationType) {
Set<A> allAnnotations = getAllAnnotations(element, annotationType);
return CollectionUtil.isEmpty(allAnnotations) ? null : allAnnotations.iterator().next();
}
/**
* 获取注解
* 若有多个则只会返回第一个注解(方法上的优先于类上的)
*
* @param element
* @param annotationType
* @param <A>
* @return
*/
public static <A extends Annotation> A getAnnotation(AnnotatedElement element, Class<A> annotationType, AnnotationSearchConfig config) {
Set<A> allAnnotations = getAllAnnotations(element, annotationType, config);
return CollectionUtil.isEmpty(allAnnotations) ? null : allAnnotations.iterator().next();
}
/**
* 获取方法和类上的注解
*
* @param element
* @return
*/
public static <A extends Annotation> Set<A> getAllAnnotations(AnnotatedElement element, Class<A> annotationType) {
return getAllAnnotations(element, annotationType, DEFAULT_SEARCH_CONFIG);
}
/**
* 获取方法和类上的注解
*
* @param element
* @return
*/
public static <A extends Annotation> Set<A> getAllAnnotations(AnnotatedElement element, Class<A> annotationType, AnnotationSearchConfig config) {
if (config == null) {
throw new NullPointerException("config can not be null");
}
if (config.isEnableCache()) {
return (Set<A>) CACHE.computeIfAbsent(getCacheKey(element, annotationType, config), k -> getAllAnnotations(element, annotationType, config.getSearchSuperMaxDepth(), config));
}
return getAllAnnotations(element, annotationType, config.getSearchSuperMaxDepth(), config);
}
/**
* 获取方法和类上的注解
*
* @param element
* @return
*/
private static <A extends Annotation> Set<A> getAllAnnotations(AnnotatedElement element, Class<A> annotationType, int depth, AnnotationSearchConfig config) {
if (element == null || annotationType == null || depth <= 0 || config == null) {
return Collections.emptySet();
}
boolean isClass = element instanceof Class;
Class<?> declaringClass = isClass ? (Class<?>) element : element.getClass();
if (element instanceof Executable) {
declaringClass = ((Executable) element).getDeclaringClass();
}
if (element instanceof Member) {
declaringClass = ((Member) element).getDeclaringClass();
}
if (element instanceof Parameter) {
declaringClass = ((Parameter) element).getDeclaringExecutable().getDeclaringClass();
}
Class<?>[] interfaces = declaringClass.getInterfaces();
Class<?> superclass = declaringClass.getSuperclass();
// 自身的注解
Set<A> methodAnnotationSet = isClass ? Collections.emptySet() : getAllMergedAnnotations(element, annotationType);
// 类上的注解
Set<A> classAnnotationSet = config.isSearchFromClass() ? getAllMergedAnnotations(declaringClass, annotationType) : Collections.emptySet();
// 接口上的注解
Set<A> interfaceAnnotationSet = config.isSearchFromInterfaces() ? (interfaces.length > 0 ? Arrays.stream(interfaces).map(interfaceCls -> getAllMergedAnnotations(interfaceCls, annotationType)).flatMap(Set::stream).collect(Collectors.toSet()) : Collections.emptySet()) : Collections.emptySet();
// 父类上的注解
Set<A> superClassAnnotationSet = config.isSearchSuperClass() ? (!Object.class.equals(superclass) ? getAllAnnotations(superclass, annotationType, depth - 1, config) : Collections.emptySet()) : Collections.emptySet();
return Stream.of(methodAnnotationSet, classAnnotationSet, interfaceAnnotationSet, superClassAnnotationSet).flatMap(Set::stream).collect(Collectors.toSet());
}
}
测试
定义了一个类,检查登录类型,用于限制某些端的用户只能访问某些端自己的接口
额外定义了多个快捷注解,快捷注解上标注了@CheckLoginType,预设了值。
方法上加注解,类上不加
调试结果
类上加注解、方法上加注解
运行调试
都能正常获取到方法、类上该注解(包括继承来的)
注解原理
注解基类Annotation,注释里写了从1.5版本引入的,所有的注解都会继承这个类,且不需要在定义的时候标明继承自这个类,只提供了annotationType来返回这个注解的Class,以及每个对象都有的equals、hashcode、toString()方法,那我们平时经常用的getAnnotation、isAnnotationPresent这些方法是哪里定义的呢?
我们通过方法找到接口为AnnotationElement,可以看到我们平时反射获取注解时的方法是定义在这个接口里的。
我们再看这个接口有哪些实现类,可以看到我们平时常用的类、方法、参数、构造方法等都实现了这个接口,因此这些地方都是可以加注解并通过反射拿到加了的注解的。
那我们看下在方法上获取注解时底层是怎么实现的,打开Method对象,可以看到它是调了父类的实现,继续往父类看
父类是Executable类
这个类是一个抽象类,上门写了是提供一些Method类和Constructor类公共使用的方法,它是一个共享的父类。
declaredAnnotations()的实现是,当前类有声明注解则直接返回当前类的declaredAnnotations这个Map,而这个map是记录的当前这个Method对象定义的一些注解,若有则直接返回这个,没有的话就会看有没有root,有root则返回root声明的所有注解的map。
而这个root对象,只有在执行Method.copy的时候才会有,那你可以当做root一直是个null的
因此平时你利用反射获取method的注解时其实就只获取到了这个方法上直接声明的注解,类上的,当前方法注解里继承的注解都不会返回的。这就是JDK方式的实现,因此spring在实现的时候提供了工具类去解决这个问题
性能测试
执行10000次,总耗时:240ms,平均耗时:0.024ms