背景
@Transactional
对Controller的某个方法进行修饰时,该Controller下的所有接口都无法访问,出现大量404
分析和问题分解
1、@Transactional
的原理是创建代理
2、Controller并无实现任何接口
3、代理类直接生成非原类型的对象,这会导致注解信息的丢失?如果符合该场景,Controller的扫描应该发生在代理创建之后
问题解决记录
1)在SpringBoot启动后,拿到ApplicationContext,通过Object getBean(String name) throws BeansException
获取对应名称的bean,查看类型:com.sun.Proxy$xxxx,很明显是jdk代理生成的匿名子类
2)查找@Transactional
注解的处理逻辑
AbstractAutoProxyCreator是继承了SmartInstantiationAwareBeanPostProcessor
接口的,也就是说在每个Bean创建之后都会经过该bean进行处理,对于匹配的bean将会创建使用BeanFactoryTransactionAttributeSourceAdvisor
的代理
3)那么如何匹配@Transactional
?
可以将注意力聚焦在AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean
方法上,其实本质上的判断是使用AopUtils#canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)
方法进行判断的,该方法是使用Advisor中的MethodMatcher对targetClass
下的所有方法进行匹配的,任意一个匹配都将会对该类创建代理对象。
4)Controller扫描的时机?
Controller扫描的核心逻辑在AbstractHandlerMethodMapping
,该类实现了InitializingBean
接口的void afterPropertiesSet() throws Exception
方法,该方法的调用时机是,在所有属性依赖被注入之后,Controller的扫描在该方法中定义。
Spring会扫描ApplicationContext中所有Bean定义,通过判断获取bean的类型,判断@Controller
和@RequestMapping
注解来确定是否为一个Controller
5)AbstractBeanFactory
的public Class<?> getType(String name) throws NoSuchBeanDefinitionException
方法返回的Bean的类型探讨
如果当前存在对应名称的单例bean,会直接返回单例对象的类型
如果Controller没有使用org.springframework.context.annotation.Scope
注解,Controller就是一个单例Bean,很明显,当Spring启动时,ApplicationContext会初始化所有非懒加载的单例bean。这时,代理对象已经创建,导致public Class<?> getType(String name) throws NoSuchBeanDefinitionException
方法取的代理对象的类型Proxy$xx
但如果Controller使用了org.springframework.context.annotation.Scope
注解,将Controller的scope变为非单例('application’是一个最接近单例的选择),我们就可以避免获取到代理对象的类型,获取到Controller的真实类型。
6)为什么使用AspectJ代理的Controller能被扫描?@Transactional
注解的却不行?
因为项目中用到了Shiro
,Shiro
注册了一个DefaultBeanFactoryPointcutAdvisor
,当使用AspectJ时,又会注册一个AnnotationAwareAspectJAutoProxyCreator
,当使用@Transactional
时,DefaultBeanFactoryPointcutAdvisor
和AnnotationAwareAspectJAutoProxyCreator
都会从BeanFactoryAdvisorRetrievalHelper
中获取Advisor
——都能取到与@Transactional
匹配的BeanFactoryTransactionAttributeSourceAdvisor
,所以导致@Transactional
的类会进行两次代理,而Aspect由于使用了切面,只能匹配到AnnotationAwareAspectJAutoProxyCreator
中为切面注册的Advisor
,而无法满足DefaultBeanFactoryPointcutAdvisor
的条件,这种现象跟DefaultBeanFactoryPointcutAdvisor
、AnnotationAwareAspectJAutoProxyCreator
的顺序无关
拓展
在日常开发中,我们通常在XXXServiceImpl中使用事务,在注入bean时,使用的是对应的接口XXXService
,但是,如果我们使用XXXServiceImpl
时,就会出现Bean Not Found
的异常,这里需要额外注意。
在Controller上使用@Aspect
切面不会导致Controller的类型丢失
原因:因为Controller往往没有使用接口,这时使用的CGLiB创建的代理,CGLIB创建代理时会实现原来的类型??????