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会失效。

原因在于二次代理

  1. 由于使用了aop:aspectj-autoproxy强制了proxy-target-class
  2. 也就是说对Web层的Class(主要是Controller)使用了CGLib代理
  3. 然后在Shiro进行代理时使用DefaultAdvisorAutoProxyCreator
  4. 原本应该判断Controller,发现没有任何接口,所以使用CGLib来代理
  5. 但是由于Controller已经被CGLib代理过一次了
  6. DefaultAdvisorAutoProxyCreator拿到对不是Contoller本身,而是CGLib的代理结果
  7. CGLib的代理结果本身是有接口的,干扰了DefaultAdvisorAutoProxyCreator的内部判断
  8. 使用JDK去代理CGLib的代理结果
  9. 结果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 注册的:

  1. 进入AspectJAutoProxyBeanDefinitionParser
  2. 找到parse函数,看到其调用registerAspectJAnnotationAutoProxyCreatorIfNecessary来注册BeanDefinition
  3. 进入registerAspectJAnnotationAutoProxyCreatorIfNecessary可以看到内部的三个register方法
  4. 三个register方法分别对应上述代码中第一个参数的输入,但是均调用了AopConfigUtils
  5. 点击AopConfigUtils,可以发现所有的方法都最终调用registerOrEscalateApcAsRequired
  6. registerOrEscalateApcAsRequired中有个简单的if…else判断
  7. 主要控制了逻辑,如果存在internalAutoProxyCreator则不进行创建新的

注意:

<aop:aspectj-autoproxy/> 方式对应的注册AutoProxyCreator 的方法是:registerAspectJAnnotationAutoProxyCreatorIfNecessary
DefaultAdvisorAutoProxyCreator 方式对应的注册AutoProxyCreator 的方法是:registerAutoProxyCreatorIfNecessary

最终会在第七步骤的函数里,由于这个if…else判断导致不能存在两个代理,所以不能混合使用 DefaultAdvisorAutoProxyCreatoraop:aspectj-autoproxy

这个结论是错误的,是因为DefaultAdvisorAutoProxyCreator不会调用registerAutoProxyCreatorIfNecessary,产生错误的原因是 二次代理 作者追踪错了代码,只是恰巧改对了.

DefaultAdvisorAutoProxyCreator

来,跟着我找 DefaultAdvisorAutoProxyCreator 如何创建代理的代码

  1. 进入DefaultAdvisorAutoProxyCreator(直接Command或者Ctrl+点击进入)
  2. 看到其继承于AbstractAdvisorAutoProxyCreator,进入
  3. 可以看到AbstractAdvisorAutoProxyCreator继承于AbstractAutoProxyCreator继续进入
  4. AbstractAutoProxyCreator中有一个方法叫做createProxy,名字太直接了,叫做创建代理
  5. 在createProxy中有一个ProxyFactory对象,就是代理的工厂模式(工厂模式请自行学习)
  6. ProxyFactory对象继承于ProxyCreatorSupport
  7. ProxyCreatorSupport中有对象aopProxyFactory用来创建AOP代理(就是切面代理,注意切面代理是属于动态代理的一种)
  8. ProxyCreatorSupport在构造函数中new了一个DefaultAopProxyFactory给aopProxyFactory赋值
  9. 进入DefaultAopProxyFactory可以看到切面代理创建方法createAopProxy
  10. 在判断条件包含 !config.isProxyTargetClass() 时,也就是不使用针对Class的代理的时候,看下一句
  11. return new JdkDynamicAopProxy(config) 根据config配置返回JDK动态代理

这也是我们在文章一中所说的,通常情况下Spring使用针对接口的JDK代理进行动态代理,绕了这么久,我们回到第五步,看 createProxy方法如何使用 ProxyFactory对象

  1. 可以看到createProxy 返回值 return proxyFactory.getProxy(this.getProxyClassLoader())
  2. 进入ProxyFactory对象的getProxy方法
  3. 可以看到 return this.createAopProxy().getProxy(classLoader)
  4. 首先调用了createAopProxy,发现ProxyFactory没有这个方法,所以来自父类 ProxyCreatorSupport
  5. 点击进入 看到createAopProxy 内包含this.getAopProxyFactory().createAopProxy(this)
  6. 首先拿到了上文第九步创建的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)    收藏  举报