spring集成shiro aop 执行两次

问题

系统整合了shiro框架后,发现方法本体执行一次,aop执行两次!
经过研究,是因为系统中有两个代理创建器,对一个通知器(通知器包含切点和通知)生成两个代理类导致的。以下是研究过程。

基本配置

spring 基本配置如下

	<context:component-scan base-package="testmaven.service"></context:component-scan>
    
    <bean id="myinterceptor" class="testmaven.interceptor.MyInterceptor"></bean>
    
    <aop:config proxy-target-class="false">
        <aop:pointcut expression="execution(*  testmaven.service.*.*(..))" id="mypointcut"/>
        <aop:advisor advice-ref="myinterceptor" pointcut-ref="mypointcut"/>
    </aop:config>

    
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>   

因为proxy-target-class=“false”,采用的是JDK代理,所以,打算将代理类生成到磁盘上,一探究竟。

为什么aop执行两次?

设置jvm参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 生成代理类后(com.sun.proxy、org.springframework.core文件夹需要手动创建),反编译观察
发现

   $Proxy13与$Proxy14都是MyService的代理类!!!

这里写图片描述

这里写图片描述

这里写图片描述

观察sayHello方法

这里写图片描述

m3方法就是MyService的sayHello方法

这里写图片描述

此处的h是什么呢?是JDKDynamicAopProxy,它实现了InvocationHandler

$Proxy14与$Proxy13基本一样,这里不再展示。

通过debug查看线程栈再次验证了上述论证,线程中依次执行了Proxy14、Proxy13的sayHello方法

这里写图片描述

至此aop执行两次的原因找到了,因为产生了两个代理。

为什么方法本体执行了一次?

按照常理,有两个代理,方法本体会执行两次,为什么本体却只执行了一次呢?
这是因为
Proxy13代理了MyService,而Proxy14又代理了Proxy13!

这也解释了为什么proxy14比proxy13多实现一个serializable接口,因为java.lang.reflect.Proxy类实现了serializable接口,所以proxy14代理proxy13时,就需要实现serializable接口了。

等同于代码

proxy14.java

public void sayHello(){
   super.h.invoke(target,args);//target就是$Proxy13
}
proxy13.java

public void sayHello(){
   super.h.invoke(target,args);//target就是MyServiceImpl
}
JDKAopDynamicProxy.java

public void invoke(){
   System.out.println("method before.....");
   //第一次,打印一次method before,此时target是$Proxy13,所以执行$Proxy13的sayHello
   //第二次,打印一次method before,此时target是MyserviceImpl,执行MyserviceImpl的sayHello
   method.invoke(target,args);
}

proxy14的target是proxy13

这里写图片描述

Proxy13代理了MyService

这里写图片描述

综上所述,aop执行两次,方法本体执行一次

解决办法

去掉DefaultAdvisorAutoProxyCreator,让系统中,只存在一个代理创建器。修改后

<context:component-scan base-package="testmaven.service"></context:component-scan>
    
    <bean id="myinterceptor" class="testmaven.interceptor.MyInterceptor"></bean>
    
    <aop:config proxy-target-class="false">
        <aop:pointcut expression="execution(*  testmaven.service.*.*(..))" id="mypointcut"/>
        <aop:advisor advice-ref="myinterceptor" pointcut-ref="mypointcut"/>
    </aop:config>

更深层次的原因

一个切面产生两个代理更深层次的原因是因为有两个代理创建器,AspectJAwareAdvisorAutoProxyCreatorDefaultAdvisorAutoProxyCreator
<aop:config/>产生了AspectJAwareAdvisorAutoProxyCreator
<aop:config/>由AopNamespaceHandler处理

public class AopNamespaceHandler extends NamespaceHandlerSupport {

	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@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());
	}

}

aop:config是由ConfigBeanDefinitionParser处理的,一路追踪,最终到了

public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
		return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
	}

可以看到<aop:config/>创建了AspectJAwareAdvisorAutoProxyCreator
再加上配置文件中的DefaultAdvisorAutoProxyCreator,就存在两个代理创建器,代理创建器会扫描配置文件中的advisor创建代理。所以对同一个切面,创建了两个代理。

总结

aop执行多次的原因是配置了多个代理创建器,多个代理创建器,产生了多个代理,代理2代理了代理1,代理1代理了本体,所以就产生了aop执行两次,本体方法执行一次的现象。
所以,系统中最好只有一个代理创建器,避免同一个通知器被创建多个代理的问题。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值