SpringBoot+shiro 注解事务不起作用探究
现象:
springboot1和shiro 配合时,service中的 @Transactional 不起作用。
一、shiro配置代理引起
Shiro在使用注解的时的配置是:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
其基于注解的权限控制功能
根据类名可以推断是通过切面的Advisor来完成的(AuthorizationAttributeSourceAdvisor)
所以说它需要创建自己的动态代理类,是由Spring的DefaultAdvisorAutoProxyCreator动态代理创建的
SpringMVC中AspectJ的配置
根据上文,启用基于注解的AspectJ就很简单了,因为基于注解的Shiro一般也在SpringMVC的Context里,我们采用如下配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
在两者分别配置的时候,配置方法都是对的但是一旦公用,会发现AspectJ会失效。
原因在于二次代理
- 由于使用了aop:aspectj-autoproxy强制了proxy-target-class
- 也就是说对Web层的Class(主要是Controller)使用了CGLib代理
- 然后在Shiro进行代理时使用DefaultAdvisorAutoProxyCreator
- 原本应该判断Controller,发现没有任何接口,所以使用CGLib来代理
- 但是由于Controller已经被CGLib代理过一次了
- DefaultAdvisorAutoProxyCreator拿到对不是Contoller本身,而是CGLib的代理结果
- CGLib的代理结果本身是有接口的,干扰了DefaultAdvisorAutoProxyCreator的内部判断
- 使用JDK去代理CGLib的代理结果
- 结果Controller的函数时去了CGLib的接口中找方法名,发现方法不存在,导致代理失败
为什么会确定是这个原因:
aspectj-autoproxy
在配置文件中的 aop:aspectj-autoproxy会最终交给名为 AopNamespaceHandler的类进行处理,进入该类(直接在工程全局搜)我们可以看到:
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
aspectj-autoproxy 是由 AspectJAutoProxyBeanDefinitionParser 注册的:
- 进入AspectJAutoProxyBeanDefinitionParser
- 找到parse函数,看到其调用registerAspectJAnnotationAutoProxyCreatorIfNecessary来注册BeanDefinition
- 进入registerAspectJAnnotationAutoProxyCreatorIfNecessary可以看到内部的三个register方法
- 三个register方法分别对应上述代码中第一个参数的输入,但是均调用了AopConfigUtils
- 点击AopConfigUtils,可以发现所有的方法都最终调用registerOrEscalateApcAsRequired
- registerOrEscalateApcAsRequired中有个简单的if…else判断
- 主要控制了逻辑,如果存在internalAutoProxyCreator则不进行创建新的
注意:
<aop:aspectj-autoproxy/> 方式对应的注册AutoProxyCreator 的方法是:registerAspectJAnnotationAutoProxyCreatorIfNecessary
DefaultAdvisorAutoProxyCreator 方式对应的注册AutoProxyCreator 的方法是:registerAutoProxyCreatorIfNecessary;
最终会在第七步骤的函数里,由于这个if…else判断导致不能存在两个代理,所以不能混合使用 DefaultAdvisorAutoProxyCreator与 aop:aspectj-autoproxy
这个结论是错误的,是因为DefaultAdvisorAutoProxyCreator不会调用registerAutoProxyCreatorIfNecessary,产生错误的原因是 二次代理 作者追踪错了代码,只是恰巧改对了.
DefaultAdvisorAutoProxyCreator
来,跟着我找 DefaultAdvisorAutoProxyCreator 如何创建代理的代码
- 进入DefaultAdvisorAutoProxyCreator(直接Command或者Ctrl+点击进入)
- 看到其继承于AbstractAdvisorAutoProxyCreator,进入
- 可以看到AbstractAdvisorAutoProxyCreator继承于AbstractAutoProxyCreator继续进入
- AbstractAutoProxyCreator中有一个方法叫做createProxy,名字太直接了,叫做创建代理
- 在createProxy中有一个ProxyFactory对象,就是代理的工厂模式(工厂模式请自行学习)
- ProxyFactory对象继承于ProxyCreatorSupport
- ProxyCreatorSupport中有对象aopProxyFactory用来创建AOP代理(就是切面代理,注意切面代理是属于动态代理的一种)
- ProxyCreatorSupport在构造函数中new了一个DefaultAopProxyFactory给aopProxyFactory赋值
- 进入DefaultAopProxyFactory可以看到切面代理创建方法createAopProxy
- 在判断条件包含 !config.isProxyTargetClass() 时,也就是不使用针对Class的代理的时候,看下一句
- return new JdkDynamicAopProxy(config) 根据config配置返回JDK动态代理
这也是我们在文章一中所说的,通常情况下Spring使用针对接口的JDK代理进行动态代理,绕了这么久,我们回到第五步,看 createProxy方法如何使用 ProxyFactory对象
- 可以看到createProxy 返回值 return proxyFactory.getProxy(this.getProxyClassLoader())
- 进入ProxyFactory对象的getProxy方法
- 可以看到 return this.createAopProxy().getProxy(classLoader)
- 首先调用了createAopProxy,发现ProxyFactory没有这个方法,所以来自父类 ProxyCreatorSupport
- 点击进入 看到createAopProxy 内包含this.getAopProxyFactory().createAopProxy(this)
- 首先拿到了上文第九步创建的DefaultAopProxyFactory,然后调用了其createAopProxy
以上完成了 DefaultAdvisorAutoProxyCreator 动态代理的创建
解决方法:
1、解决办法也很简单,注释掉Shiro配置中的第一句。(成功)
<aop:config proxy-target-class="true"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
2、给DefaultAdvisorAutoProxyCreator加入参数proxyTargetClass为true(不起作用,原因看二)
参考:http://www.jkeabc.com/311648.html
二、事务配置二次代理问题
出问题的配置:配置1
<bean class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"> <property name="proxyTargetClass" value="true"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
此配置的目的是想进行cglib类代理。但是实际上当进行直接注入类,而不是接口时会找不到Bean错误。
但是如果是这样配置:配置2
<aop:aspectj-autoproxy proxy-target-class="true"/> <tx:annotation-driven transaction-manager="transactionManager"/>
此配置可以很好的工作,并注入类(不是接口)。
分析:<aop:aspectj-autoproxy proxy-target-class="true"> 源码一已经分析。
分析:<tx:annotation-driven>
该命名空间交给org.springframework.transaction.config.TxNamespaceHandler处理:
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
其中<annotation-driven> 会交给AnnotationDrivenBeanDefinitionParser进行解析:
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
registerTransactionalEventListenerFactory(parserContext);
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerTransactionAspect(element, parserContext);
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}
默认mode="proxy",所以走AopAutoProxyConfigurer.configureAutoProxyCreator,其代码中第一句话是:
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
此处我们又看到了registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME),如果是:
- 配置1,那么实际是两个AutoProxyCreator。前面<bean class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator">定义了一个AutoProxyCreator,后面的<tx:annotation-driven transaction-manager="transactionManager"/>也定义另一个AutoProxyCreator。
- 配置2,那么实际是共用一个AutoProxyCreator;
而且如果配置1时,因为我们没有指定<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> 所以是JDK动态代理,因此不管怎么样,都无法注入类的。
如何解决
- 给配置1起名字为”org.springframework.aop.config.internalAutoProxyCreator“;
- 或者使用配置2
https://www.jianshu.com/p/ac1adadf854c
三、AOP源码分析
主要分析:AbstractAutoProxyCreator
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
总结:
1、如果没有必要,请不要使用低级别API,如上述-->自己去创建AutoProxyCreator
2、首先选择使用如:
<aop:config>或者是<org.springframework.aop.config.internalAutoProxyCreator>
如上配置已经非常好了,根本没必要使用低级别API。
如<tx:annotation-driven>使用的AutoProxyCreator都是和上边是一样的。这样还能防止二次代理。另外 有时不可避免的自定义配置,则需要了解Spring源码处理机制,否则很难解决实际问题。
posted on 2018-06-10 17:42 TrustNature 阅读(35) 评论(0) 收藏 举报
浙公网安备 33010602011771号