Spring中如何获取Bean方法上的自定义注解

背景描述

项目中需要扫描出来所有 标注了自定义注解A的Service里面标注了自定义注解B的方法 来做后续处理。

基本的思路就是通过Spring提供的ApplicationContext#getBeansWithAnnotation+反射 来实现。

但是,随着在Service里面引入了声明式事务(@Transactional),上述的方法也就随之失效。

场景复现

这里通过构造一个case来说明问题

Service上的注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MyService {
}

方法上的注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnno {
    String value() default "";
}

Service代码

public interface UserService {
    void print();
}

@MyService
@Component("annoUserService")
public class UserServiceImpl implements UserService {
    @Override
    @MyAnno("xujianadgdgagg")
    public void print() {
        System.out.println("写入数据库");
    }
}

自定义注解扫描代码

@Component
public class FindAnnotationService {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
    // 获取带有自定义注解的bean
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
        for (Object bean : beanMap.values()) {
            Class<?> clazz = bean.getClass();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
            // 寻找带有自定义注解的方法
            if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
            // 如果方法上有自定义注解,则获取这个注解
                MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
                System.out.println(annotation.value());
            }
        }
    }
}

测试类

@SpringBootTest
public class FindAnnotationServiceTests {
    @Autowired
    private UserService annoUserService;

    @Test
    public void testPrint() {
        annoUserService.print();
    }
}

当对UserServiceImpl#print()方法加上@Transactional注解时,上面获取bean的地方,拿到的已经不是UserServiceImpl对象了,而是一个CGLIB代理类,如下所示:
在这里插入图片描述

我们都知道Spring确实会为声明式事物生成代理类。

对这个代理类通过反射并没有获取到带有自定义注解的方法。

问题追踪

最直接的原因推测是生成的代理类并不包含原始类中用户自定义的注解。

CGLIB动态代理以及生成的代理类可以参考《深入理解JVM字节码》。

为了验证猜想,我们自己手动为UserServiceImpl生成一个CGLIB代理类,同时去掉@Transactional注解。
这里通过BeanPostProcessor创建代理类:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // CGLIB动态代理
            MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
            myMethodInterceptor.setTarget(bean);
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(bean.getClass());
            enhancer.setCallback(myMethodInterceptor);
            return enhancer.create();
        } else {
            return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
        }
    }
}

public class MyMethodInterceptor implements MethodInterceptor {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib增强目标方法");
        return method.invoke(target,objects);
    }
}

结果跟刚才一样,由于生成了代理类而获取不到自定义注解。

解决方案

既然CGLIB代理类是罪魁祸首,那就得从它下手。

由于CGLIB生成的代理类继承了原始类,那在拿到这个代理类的时候,去找到它的父类(原始类),不就可以拿到自定义注解了吗?

对代码作如下改动:

@Component
public class FindAnnotationService {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
    // 获取带有自定义注解的bean
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
        for (Object bean : beanMap.values()) {
            Class<?> clazz = bean.getClass();
            // 获取父类(代理类的原始类)
            clazz = clazz.getSuperclass();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
            // 寻找带有自定义注解的方法
            if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
                MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
                System.out.println(annotation.value());
            }
        }
    }
}

在这里插入图片描述
这样果然拿到了自定义注解。

对于这种情况,Spring早已预判到了,并提供了一个工具方法AnnotationUtils.findAnnotation用来获取bean方法上的注解,不管这个bean是否被代理。

通过这个工具方法优化代码如下:

@Component
public class FindAnnotationService {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(MyService.class);
        for (Object bean : beanMap.values()) {
            Class<?> clazz = bean.getClass();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.isAnnotationPresent(MyAnno.class)) {
                MyAnno annotation = declaredMethod.getDeclaredAnnotation(MyAnno.class);
                System.out.println(annotation.value());
            }
        }
    }
}

扩展思考

既然CGLIB动态代理有这种问题,那JDK动态代理呢?

手动为UserServiceImpl生成JDK动态代理:

@Component
public class MyBeanPostProcess implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserService) {
            // JDK动态代理
            MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
            myInvocationHandler.setTarget(bean);
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), bean.getClass().getInterfaces(), myInvocationHandler);
        } else {
            return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
        }
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("增强目标方法");
        return method.invoke(target,args);
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}

在不使用AnnotationUtils.findAnnotation的时候果然还是获取不到自定义注解。

但是加上AnnotationUtils.findAnnotation以后发现还是获取不到!!!

为了探究原因,对AnnotationUtils.findAnnotation源码作简要分析以后发现:

AnnotationsScanner#processMethodHierarchy(C context, int[] aggregateIndex, Class<?> sourceClass, AnnotationsProcessor<C, R> processor, Method rootMethod, boolean includeInterfaces)

            // 如果当前代理类实现了接口(JDK动态代理方式)
            if (includeInterfaces) {
                Class[] var14 = sourceClass.getInterfaces();
                var9 = var14.length;

                for(var10 = 0; var10 < var9; ++var10) {
                    Class<?> interfaceType = var14[var10];
                    // 对实现的接口递归寻找注解
                    R interfacesResult = processMethodHierarchy(context, aggregateIndex, interfaceType, processor, rootMethod, true);
                    if (interfacesResult != null) {
                        return interfacesResult;
                    }
                }
            }

            // 如果当前代理类有父类(CGLIB动态代理方式)
            Class<?> superclass = sourceClass.getSuperclass();
            if (superclass != Object.class && superclass != null) {
                // 对父类递归寻找注解
                R superclassResult = processMethodHierarchy(context, aggregateIndex, superclass, processor, rootMethod, includeInterfaces);
                if (superclassResult != null) {
                    return superclassResult;
                }
            }

我们知道CGLIB代理是基于继承原始类来实现的,而JDK代理是基于实现接口来实现的。

从上面的源码可以大致判断出:对于CGLIB代理通过递归搜寻父类来找注解;对于JDK代理通过递归搜寻实现的接口来找注解。

那么在使用JDK生成代理的时候,把自定义注解放在接口UserService的方法上,而不是实现类UserServiceImpl上:

public interface UserService {
    @MyAnno("xujianadgdgagg")
    void print();
}

这样就可以通过AnnotationUtils.findAnnotation成功获取自定义注解了~

其实现在Spring大部分都是通过CGLIB生成的代理,所以无需将自定义注解放在接口上,毕竟放在实现类上才是常规操作。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
获取添加了自定义注解的所有方法,你可以使用 Spring 的反射机制。下面是一个示例代码,演示了如何获取添加了指定注解的所有方法: ```java import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @Component public class AnnotationMethodProcessor implements BeanPostProcessor { private List<Method> annotatedMethods = new ArrayList<>(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Class<?> clazz = bean.getClass(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (AnnotationUtils.findAnnotation(method, YourCustomAnnotation.class) != null) { annotatedMethods.add(method); } } return bean; } public List<Method> getAnnotatedMethods() { return annotatedMethods; } } ``` 在上面的示例代码,首先,你需要在自定义的注解处理器类(例如上述示例的 `AnnotationMethodProcessor`)上添加 `@Component` 注解,以便让 Spring 容器自动扫描并进行初始化。然后,你可以在 `postProcessBeforeInitialization` 方法使用 `AnnotationUtils.findAnnotation` 方法来判断方法是否添加了指定的自定义注解,并将这些方法添加到 `annotatedMethods` 列表。 接下来,你可以在需要获取添加了自定义注解的所有方法的地方使用 `AnnotationMethodProcessor` 类。例如: ```java List<Method> annotatedMethods = annotationMethodProcessor.getAnnotatedMethods(); ``` 这样,`annotatedMethods` 列表就包含了添加了自定义注解的所有方法的 `Method` 对象。你可以根据需要进一步处理这些方法。请注意,你需要保证 `AnnotationMethodProcessor` 类已经被 Spring 容器正确初始化,才能获取到正确的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值