文档章节

Spring AOP源码分析(八)SpringAOP要注意的地方

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:22
字数 1154
阅读 417
收藏 2
SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来:
(1)SpringAOP对于最外层的函数只拦截public方法,不拦截protected和private方法,另外不会对最外层的public方法内部调用的其他方法也进行拦截,即只停留于代理对象所调用的方法。如下案例:
B类有两个public方法,foo1()和foo2(),foo1内部调用了foo2,简单如下:

public void foo2() {  
        System.out.println("foo2");  
    }  
    
    public void foo1(){
    	System.out.println("foo1");
    	this.foo2();
    }

假如都对这两个方法进行拦截。当你调用,B对象.foo1()仅仅对foo1整个方法拦截,对于它内部调用的foo2()方法不会进行拦截。
源码分析:
判断上述this.foo2()方法是否被拦截的最本质的东西是看this到底是谁?有如下对象B类的对象b,和cglib生成的代理对象bProxy,代理对象bProxy内部拥有b。如果调用b对象的任何方法,肯定不会发生任何拦截,当调用bProxy的方法则都会进入拦截函数。

当我们调用bProxy对象的foo1()方法时,先执行cglib之前设置的callback对象的intercept拦截函数,如下:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Class<?> targetClass = null;
			Object target = null;
			try {
				if (this.advised.exposeProxy) {
					// Make invocation available if necessary.
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// May be null. Get as late as possible to minimize the time we
				// "own" the target, in case it comes from a pool...
				target = getTarget();
				if (target != null) {
					targetClass = target.getClass();
				}
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// Check whether we only have one InvokerInterceptor: that is,
				// no real advice, but just reflective invocation of the target.
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					// We can skip creating a MethodInvocation: just invoke the target directly.
					// Note that the final invoker must be an InvokerInterceptor, so we know
					// it does nothing but a reflective operation on the target, and no hot
					// swapping or fancy proxying.
					retVal = methodProxy.invoke(target, args);
				}
				else {
					// We need to create a method invocation...
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null) {
					releaseTarget(target);
				}
				if (setProxyContext) {
					// Restore old proxy.
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

这个过程之前的文章已经分析过,这里就是首先取出拦截器链List<Object> chain,当foo1方法不符合我们所配置的pointcut时,拦截器链必然为空,然后就是直接执行目标对象的方法。
当foo1方法符合所配置的pointcut时,拦截器链不为空,执行相应的通知advice,currentInterceptorIndex 从-1开始,如下:

public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

随着通知不断的传递执行,最终this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1将会满足条件,将会来到执行目标对象的方法invokeJoinpoint():
protected Object invokeJoinpoint() throws Throwable {
			if (this.publicMethod) {
				return this.methodProxy.invoke(this.target, this.arguments);
			}
			else {
				return super.invokeJoinpoint();
			}
		}

在这里不管要拦截的目标方法是不是public方法,最终所传递的对象都是this.target,他是目标对象而不是代理对象,即执行上述foo1()函数的对象是目标对象而不是代理对象,所以它内部所调用的this.foo2()也是目标对象,因此不会发生拦截,如果是执行的是代理对象.foo2()则必然会进入intercept拦截过程。所以上述调用foo1函数,其内部调用的foo2函数是不会发生拦截的,因为this指的是目标对象,不是代理对象。

如果你想实现foo1调用时内部的foo2也进行拦截,就必须把this换成代理对象。这时就要用到了,xml配置中的expose-proxy="true",即暴露出代理对象,它使用的是ThreadLocal设计模式,我们可以这样获取代理对象,AopContext.currentProxy()就是代理对象,然后转换成目标对象或者目标接口,执行相应的方法:

public void foo1(){
    	System.out.println("run foo1");
    	BServiceImpl proxy=(BServiceImpl) AopContext.currentProxy();
    	proxy.foo2();
    }


最后再给出Spring的文档说明:
Due to the proxy-based nature of Spring’s AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn’t applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!

If your interception needs include protected/private methods or even constructors, consider the use of Spring-driven native AspectJ weaving instead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.

若想转载请注明出处
作者:乒乓狂魔

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 996
博文 105
码字总数 271356
作品 0
长宁
程序员
Spring AOP就是这么简单啦

前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模块~ 之前我已经写过一篇关于AOP的文章了,那篇把比较重要的知识点都讲解过了一篇啦:Sprin...

Java3y
05/24
0
0
Spring系列之Spring框架和SpringAOP集成过程分析(十)

在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里的分析都是基于AspectJ注解的)。我们需要在我们的Spring配置文件中引入SpringAOP的命名空间和标签,然后定义切面Bea...

木叶之荣
06/18
0
0
Spring AOP介绍及源码分析

一、AOP介绍 举个例子来说明一下吧!现在系统中有很多的业务方法,如上传产品信息、修改产品信息、发布公司库等;现在需要对这些方法的执行做性能监控,看每个业务方法的执行时间;在不改变原...

阿扬丶
2013/11/07
0
3
Spring AOP 的实现方式(以日志管理为例)

Spring AOP 的实现方式(以日志管理为例) 2016年10月08日 00:13:57 阅读数:23198 在学习Spring框架的历程中,最重要的是要理解Spring的IOC和AOP了,不但要学会怎么用,最好是知道它是怎么实...

Jeam_
07/04
0
0
Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的。我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反射创建一个原始的 ...

coolblog.xyz
06/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

打开eclipse出现an error has occurred see the log file

解决方法: 1,打开eclipse安装目录下的eclipse.ini文件; 2,打开的文本文件最后添加一行 --add-modules=ALL-SYSTEM 3,保存重新打开Eclipse。...

任梁荣
4分钟前
0
0
搞定Northwind示例数据库,无论哪个版本的SQLServer都受用

Northwind数据库 从这里可以找到突破口: http://social.msdn.microsoft.com/Forums/zh-CN/Vsexpressvb/thread/8490a1c6-9018-40c9-aafb-df9f79d29cde 下面是MSDN: http://msdn2.microsoft......

QQZZFT
17分钟前
0
0
mysql主从同步,安装配置操作

准备 两台mysql服务,我这里准备了如下: 主库:192.168.176.128 从库:192.168.176.131 如何在Linux上安装mysql服务,请看https://blog.csdn.net/qq_18860653/article/details/80250499 操作...

小致dad
22分钟前
0
0
一个手机装天下,走遍中国都不怕!

导读 “1200元(人民币,下同),微信支付,可以,你扫我。”来自西非马里共和国的展商Albert拿着手机,和一位买走他手鼓的中国游客用简单的汉语交流着。 近日,“第十四届中俄蒙经贸洽谈暨商品...

问题终结者
28分钟前
0
0
Redis的“死键”问题

大规模的数据库存储系统中,数据的生命周期管理是很有必要的;从业务角度发现过期数据,数据归档和数据碎片整理等。以MySQL为例,1个运行很久的TB级MySQL实例中,极有可能数百GB的数据,对业...

IT--小哥
29分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部