工作中时长用到自定义注解的场景,光定义了还不行,有时候还需要在服务启动的时候扫描出来并缓存,然后再做后续的处理,但是扫描的代码基本一致,不想每次都写扫描的方法怎么办,所有我就有了一个大胆的想法。
基于之前写过一个查找泛型的实际类型的文章,基于查找泛型的实际类型工具类,有了如下的逻辑。
比如我们定义一个接口,该接口有一个方法dealAnnotation(方法上的注解,方法信息,方法所在对象信息),如下:
/**
* 被A注解修饰的方法,会调用{@link IScanMethod#dealAnnotation(Annotation, Method, Object)}方法
*
* @param <A>
*/
public interface IScanMethod<A extends Annotation> {
/**
* @param annotation 扫描的方法上的注解
* @param method 方法
* @param beanObj 该方法所在类的实例
*/
void dealAnnotation(A annotation, Method method, Object beanObj);
}
然后我们定义一个扫描类,该类主要负责扫描实现了IScanMethod接口的方法,并获取该接口的泛型的实际对象,然后扫描所有的bean,通过反射获取bean的方法,循环遍历方法,看有没有IScanMethod接口泛型定义的注解,如果有,回调对应的IScanMethod接口实现类,然后由实现类负责处理对应注解的逻辑
/**
* 管理所有实现了{@link IScanMethod}接口的实现类,
* <p>
* 在{@link IScanMethod#dealAnnotation(Annotation, Method, Object)}方法中处理即可
*/
@Component
public class ScanAnnotationMethodManager implements InitializingBean {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
protected ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() {
Map<Class<? extends Annotation>, List<IScanMethod>> classListMap = new HashMap<>();
// 获取所有实现了该接口的bean
String[] beanNames = applicationContext.getBeanNamesForType(IScanMethod.class);
for (String beanName : beanNames) {
Class<?> handlerType = applicationContext.getType(beanName);
IScanMethod scanAnnotationBean = (IScanMethod) applicationContext.getBean(beanName);
// 获取该bean的实际类型
final Class<?> consumerType = ClassUtils.getUserClass(handlerType);
// 查找该bean的注解泛型的实际类型
Class<?> scanAnnotationClass = XGenericUtils.formByClass(consumerType, IScanMethod.class, 0);
if (!classListMap.containsKey(scanAnnotationClass)) {
classListMap.put((Class<? extends Annotation>) scanAnnotationClass, new ArrayList<>());
}
if (logger.isDebugEnabled()) {
logger.debug("扫描IScanMethod实现类:{},处理注解:{}", scanAnnotationBean, scanAnnotationClass);
}
classListMap.get(scanAnnotationClass).add(scanAnnotationBean);
}
scanAllMethod(classListMap);
}
/**
* 扫描所有方法,判断方法上是否带有指定注解
*
* @param classListMap
*/
private void scanAllMethod(Map<Class<? extends Annotation>, List<IScanMethod>> classListMap) {
// 获取所有的bean类型
String[] beanNames = applicationContext.getBeanNamesForType(Object.class);
Set<Class<? extends Annotation>> classes = classListMap.keySet();
for (String beanName : beanNames) {
Class<?> handlerType = applicationContext.getType(beanName);
Object bean = applicationContext.getBean(beanName);
Class<?> consumerType = ClassUtils.getUserClass(handlerType);
// 循环遍历该bean的所有方法
XMethodUtils.scanAllMethod(consumerType, method -> classes.forEach(aClass -> exeMethod(method, aClass, classListMap.get(aClass), bean)));
}
}
private void exeMethod(Method method, Class<? extends Annotation> aClass, List<IScanMethod> scanMethods, Object bean) {
boolean hasAnnotation = AnnotatedElementUtils.hasAnnotation(method, aClass);
// 不包含指定的注解,直接return
if (!hasAnnotation) return;
Annotation annotation = method.getAnnotation(aClass);
if (logger.isDebugEnabled()) {
logger.debug("扫描方法:{},来自注解:{}", method, annotation);
}
// 包含指定的注解,调用处理的方法
scanMethods.forEach(scanMethod -> scanMethod.dealAnnotation(annotation, method, bean));
}
}
好了,当我们再扫描那行方法带有自定义注解的时候,只需要这么做
1,定义一个处理类,并使用@Component进行修饰
2,实现IScanMethod接口,泛型为自定义的注解 ScanFilter
3,在dealAnnotation处理自己的逻辑就可以了
/**
* 扫描被ScanFilter注解修饰的方法,
*/
@Component
public class ScanFilterMethodManager implements IScanMethod<ScanFilter> {
@Autowired
private ApplicationContext applicationContext;
@Override
public void dealAnnotation(ScanFilter annotation, Method method, Object beanObj) {
// 处理自己的逻辑
......
}
}
XMethodUtils实现:该类为Spring底层的一个工具类,拿过来稍微改动了一下,该类主要实现循环遍历指定class的方法,并且能去除重复的方法,如父类有个A方法,子类重新了A方法,该处只会遍历一次
public class XMethodUtils {
/**
* 扫描所有用户自己定义的方法,并调用{@link Consumer}接口
*
* @param targetType
* @param methodCall
*/
public static void scanAllMethod(Class<?> targetType, Consumer<Method> methodCall) {
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Set<Method> methods = new HashSet<>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (methods.contains(bridgedMethod)) return;
methods.add(bridgedMethod);
methodCall.accept(bridgedMethod);
}, ReflectionUtils.USER_DECLARED_METHODS);
}
}
}