背景描述
项目中需要扫描出来所有 标注了自定义注解A的Service里面标注了自定义注解B的方法 来做后续处理。
基本的思路就是通过Spring提供的ApplicationContext#getBeansWithAnnotation
+反射 来实现。
但是,随着在Service里面引入了声明式事务(@Transactional
),上述的方法也就随之失效。
场景复现
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MyService {
}
方法上的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnno {
String value() default "";
}
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代理类,如下所示:
问题追踪
最直接的原因推测是生成的代理类并不包含原始类中用户自定义的注解。
为了验证猜想,我们自己手动为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动态代理呢?
@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代理通过递归搜寻实现的接口来找注解。
那么在使用JDK生成代理的时候,把自定义注解放在接口UserService
的方法上,而不是实现类UserServiceImpl
上:
public interface UserService {
@MyAnno("xujianadgdgagg")
void print();
}
这样就可以通过AnnotationUtils.findAnnotation
成功获取自定义注解了~
原文地址:https://blog.csdn.net/qq_18515155/article/details/131128212
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_37192.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!