在深入解析SpringAOP设计原理之前,我们需要了解SpringAOP中的几个核心概念:
1 连接点(jointpoint): 一个连接点是一个程序执行过程中的特定点。电信的连接点包括对一个方法的调用,方法执行的过程本身,类的初始化,对象的实例化等。连接点是AOP的核心概念之一,它用来定义在程序中的什么地方能够通过AOP加入额外的逻辑。
2 通知(advice):在某一特定的连接点出运行的代码成为通知,通知有很多种,比如在连接点之前执行的前置通知(before advice)和在连接点之后执行的后置通知(after advice)。
3 切入点(pointcut):切入点是用来定义某一个通知该何时执行的一组连接点。通过创建切入点,我们可以精确地控制程序中什么组件接到什么通知。之前我们提到过,一个典型的连接点是方法的调用,而一个典型的切入点就是对某一个类的所有方法的调用的集合。
4 方面(aspect ):通知和切入点的组合叫做方面。这个组合定义了一段程序中应该包括的逻辑和以及何时执行该逻辑。
5 织入(weaving):织入是将方面真正加入程序代码的过程。对于编译时AOP方案而言,织入自然是在编译时完成的,它通常是作为编译过程中的一个额外的步骤。类似的对于运行时AOP方案,织入过程是在程序运行时动态执行的。
6目标(target):如果一个对象的执行过程收到某个AOP操作的修改,那么它就叫做一个目标对象,目标对象通常也称为被通知对象。
7引入(introduction):通过引入,我们可以在一个对象中加入新的方法或者字段,以改变它的结构,它可以使用引入来让你可以使用引入来让任何对象实现一个特定的接口,而不需要这个对象的类显式地实现这个接口。
第一步:生成代理对象:
这里我们已经能够确定Spring是如何生成代理对象的,最终它是通过JdkDynamicAopproxy对象和Cglib2AopProxy来生成AopProxy代理对象。
现在我们又有了新的疑问:Spring又是如何通过使代理对象的拦截机制发挥作用的?
接下来我们进入Spring AOP拦截调用的实现
首先是JdkDynamicAopProxy 的invoke拦截:
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
这里我们可以看到采用了JDK动态代理:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
loader是类装载器
interfaces是目标对象实现的一系列接口
h是一个实现InvocationHandler接口的类, 我们对代理对象的所有操作都经过它处理
因为JdkDynamicAopProxy实现了InvocationHandler接口故可以采用this。也就是说Proxy对象的代理方法被调用时,JdkDynamicAopProxy的invoke方法作为Proxy对象的回调函数而被触发,从而完成目标对象方法的拦截或者功能增强的工作。在这个invoke方法中包含了一个完整的拦截器链对目标对象的的拦截过程,比如获得拦截器链并对其中的拦截器进行配置,运行拦截器链里的拦截增强直到目标方法的调用等。
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// 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 = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
return retVal;
它首先会获得定义好的拦截器链,如果没有设定拦截器,那么直接嗲用target对应的方法。
如果设定了拦截器链,那么需要调用拦截器之后才会调用目标对象的相应的方法,具体是通过构造一个ReflectiveMethodInvocation来实现。
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);
}
}
如果拦截器链中的拦截器迭代调用完毕,开始调用target函数,这个函数是通过反射机制完成的,具体是现在AopUtils.invokeJoinpointUsingReflection的方法中。
接下来对拦截器进行动态的匹配判断如果匹配则执行,如果不陪陪则递归调用知道所有的拦截器都被运行为止。
现在我们知道了代理中的拦截器(也就是通知)是如何被调用的,但是这些通知器是设么时候配置的,以及如何配置的呢?
记得我们上面说了在调用JdkDynamicAopProxy的invoke方法是:要先去获取拦截器链。最终是DefaultAdvisorChainfactory生成拦截器链:它会通过一个AdvisorAdapterRegistry来实现拦截器的适配和注册。
注意:
Spring的AOP是通过代理来实现的:
而Spring代理有两种方式:
一种是JDK动态代理(JdkDynamicAopProxy),一种是Cglib代理(Cglib2AopProxy)
使用JDK代理时,Java虚拟机 会拦截所有方法的调用,并将其传至代理的invoke方法,然后invoke方法决定该方法有没有被通知
如果有,那么调用通知链。这就意味着JDK动态代理在调用invoke方法之前不能判断一个方法是否接收通知。这就意味着,即使对于代理
上没有通知的方法,invoke方法之前不能判断一个方法是否被调用,所有的检查度会被进行一遍,最后该方法还是会被反射回来调用,
使用Cglib时会在运行时随时为代理创建新的字节码,并且尽可能的重用已经生成的字节码,其次Cglib会询问Spring中每个方法应该如何处理,这就意味着很多决定在
invoke调用时都要进行,而在cglib中只用进行一次。
具体选用哪种代理,CGLIB可以代理接口,也可以代理类,而JDK代理只能代理接口。同时Cglib的性能远远超过JDK动态代理。但是需要注意的是:它会为每一个不同的代理生成一个新类,虽然自1.1版开始,Spring能正确的重用生成的类
从而降低频繁的类生成带来的运行开销和CGLIB代理类所用的内存。
在我们使用SpringAOP时需要引入的包有(这里的代理内部是通过JDK动态代理(JdkDynamicAopProxy)实现的):
aopalliance.jar 如果要用AspectJ则要引入eclipse.aspectj.rt和eclipse.aspectj.weaver.jar
如果要使用Cglib代理,我们需要引入的包cglib.jar,asm.jar(cglib中操作操作字节码的部分是通过asm来实现的,所以cglib要依赖于asm)
我们分别引入两个jar包,运行程序后出错:主要是因为版本不匹配造成的。
最后搜索得知,我们可以直接引入cglib-nodep.jar(它封装了Cglib和asm,我们不需要考虑版本不匹配的问题)