工作中时长用到自定义注解的场景,光定义了还不行,有时候还需要在服务启动的时候扫描出来并缓存,然后再做后续的处理,但是扫描的代码基本一致,不想每次都写扫描的方法怎么办,所有我就有了一个大胆的想法。

基于之前写过一个查找泛型的实际类型的文章,基于查找泛型的实际类型工具类,有了如下的逻辑。

比如我们定义一个接口,该接口有一个方法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);
        }
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐