37--SpringAop代理调用过程(一)
2018-12-20 本文已影响42人
闲来也无事
引
经过前两篇的分析,已经成功创建了目标类的代理,接着分析代理的调用过程。在前面的章节已经介绍过SpringAOP中的增强类型分别有前置增强、后置异常增强、后置返回增强、后置最终增强、环绕增强五种类型,从名称上我们也可以大致看出来前置增强一定是先于后置增强被执行的,那么SpringAOP是如何保证这几种增强的执行顺序呢?它们的执行顺序应该什么样呢?
在35--SpringAop创建代理(一) 中已经介绍过Spring在获取到所有增强之后,还要筛选出来适合当前bean的增强。
再筛选完之后,其实还有两个很重要的步骤,我们没有分析,那就是在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增强(方法拦截)以及对增强进行排序
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 1、查找所有候选增强
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 2、从所有增强集合中查找适合当前bean的增强
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
// 3、在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增强
// ExposeInvocationInterceptor的作用是可以将当前的MethodInvocation暴露为一个thread-local对象,该拦截器很少使用
// 使用场景:一个切点(例如AspectJ表达式切点)需要知道它的全部调用上线文环境
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 4.对增强进行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
关于ExposeInvocationInterceptor无需过多了解,注释里已经有所介绍,重点看对增强的排序原则。
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
// 1.创建PartiallyComparableAdvisorHolder集合并将所有的增强加入到该集合中
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
for (Advisor element : advisors) {
partiallyComparableAdvisors.add(new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
}
// 2.执行排序并返回结果
List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
}
else {
return super.sortAdvisors(advisors);
}
}
具体的排序方法不做过深入的分析了,但是其排序的原则和增强的执行顺序还是要简单介绍一下:
- 如果在一个切面中没有相同类型的增强(例如:一个切面类里不同时有两个前置增强),且目标方法里没有异常抛出,那么其执行顺序是:环绕增强 --> 前置增强 --> 目标类方法 --> 前置增强 --> 后置最终增强 --> 后置返回增强
- 如果在一个切面中没有相同类型的增强(例如:一个切面类里不同时有两个前置增强),但目标方法里存在异常,那么其执行顺序是 :环绕增强 --> 前置增强 --> 目标类方法(直至调用到发生异常的代码)--> 后置最终增强--> 后置异常增强
- 如果两条相同增强类型增强来自同一切面,它们将具有相同的顺序。然后根据以下规则进一步排序。如果是后置增强,那么最后声明的增强将获得最高的优先级,对于其他类型的增强,首先声明的增强将获得最高的优先级
其执行顺序已经了解了,下面通过两个实例才验证一下(另外在前面的文章介绍稍微有些缺陷,这里也修正一下)。
2. 增强调用顺序实例
- 目标类
package com.lyc.cn.v2.day07;
public interface Animal {
void sayHello();
void sayException();
}
package com.lyc.cn.v2.day07;
public class Dog implements Animal {
@Override
public void sayHello() {
System.out.println("--被增强的方法");
}
@Override
public void sayException() {
// 手动抛出异常
System.out.println("--被增强的方法,准备抛出异常");
throw new IllegalArgumentException();
}
}
- 切面类
package com.lyc.cn.v2.day07;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.junit.experimental.theories.Theory;
/**
* 切面类
* @author: LiYanChao
* @create: 2018-10-31 15:46
*/
@Aspect
//@Aspect("perthis(this(com.lyc.cn.v2.day07.Dog))")
//@Aspect("pertarget(this(com.lyc.cn.v2.day07.Dog))")
public class DogAspect {
/**
* 例如:execution (* com.sample.service.impl..*.*(..)
* 1、execution(): 表达式主体。
* 2、第一个*号:表示返回类型,*号表示所有的类型。
* 3、包名:表示需要拦截的包名,后面的两个点表示当前包和当前包的所有子包,
* 即com.sample.service.impl包、子孙包下所有类的方法。
* 4、第二个*号:表示类名,*号表示所有的类。
* 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个点表示任何参数。
**/
@Pointcut("execution(* com.lyc.cn.v2.day07.*.*(..))")
public void test() {
}
@Before("test()")
public void beforeTest() {
System.out.println("==前置增强");
}
@After("test()")
public void afterTest() {
System.out.println("==后置最终增强");
}
@AfterThrowing(value = "test()", throwing = "th")
public void afterThrowingTest(JoinPoint jp, Throwable th) {
System.out.println("==后置异常增强,连接点信息:" + jp.getSignature());
System.out.println("==后置异常增强,异常信息:" + th);
}
@AfterReturning("test()")
public void afterReturningTest() {
System.out.println("==后置返回增强");
}
// 如果在环绕增强里手动处理了异常的话,那么后置异常增强是无法被调用到的
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p) throws Throwable {
System.out.println("==环绕增强开始");
Object o = p.proceed();
System.out.println("==环绕增强结束");
return o;
}
// @DeclareParents(value = "com.lyc.cn.v2.day07.Dog", defaultImpl = IntroduceImpl.class)
// private IIntroduce iIntroduce;
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1、proxy-target-class
如果被代理的目标对象至少实现了一个接口,则会使用JDK动态代理,所有实现该目标类实现的接口都将被代理
如果该目标对象没有实现任何接口,则创建CGLIB动态代理。
但是可以通过proxy-target-class属性强制指定使用CGLIB代理,
2、expose-proxy
解决目标对象内部的自我调用无法实施切面增强的问题
-->
<aop:aspectj-autoproxy>
<!-- 指定@Aspect类,支持正则表达式,符合该表达式的切面类才会被应用-->
<!--<aop:include name="dogAspect"></aop:include>-->
</aop:aspectj-autoproxy>
<!--AspectJ-->
<bean name="dogAspect" class="com.lyc.cn.v2.day07.DogAspect"/>
<!--<bean name="catAspect" class="com.lyc.cn.v2.day07.CatAspect"/>-->
<!--bean-->
<bean id="dog" class="com.lyc.cn.v2.day07.Dog"/>
</beans>
- 测试方法
@Test
public void test1() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayHello();
}
@Test
public void test5() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayException();
}
这样准备工作做完了,来看具体的测试结果:
- 没有异常抛出
==环绕增强开始
==前置增强
--被增强的方法
==环绕增强结束
==后置最终增强
==后置返回增强
- 有异常抛出
==环绕增强开始
==前置增强
--被增强的方法,准备抛出异常
==后置最终增强
==后置异常增强,连接点信息:void com.lyc.cn.v2.day07.Animal.sayException()
==后置异常增强,异常信息:java.lang.IllegalArgumentException
对于增强的执行顺序我们已经有所了解,下面通过分析源码,来看一下Spring是如何执行代理方法调用的。
3.JDK动态代理调用过程(假设无异常抛出情况下)
这里,如何进入到其调用源码里呢,我们可以在前面的测试类dog.sayHello();
这句话前面打断点,然后步入断点就OK了。
/**
* 调用JDK动态代理invoke方法
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 1、处理equals方法,如果接口中没有定义equals而在实现类中覆盖了equals方法,那么该equals方法不会被增强
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
// 2、处理hashCode方法,如果接口中没有定义hashCode而在实现类中覆盖了hashCode方法,那么该hashCode方法不会被增强
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
// 3、如果目标对象是DecoratingProxy类型,则返回目标对象的最终对象类型
// DecoratingProxy接口只有一个getDecoratedClass方法,用于返回目标对象的最终对象类型
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
// 4、如果目标对象是Advice类型,则直接使用反射进行调用
// opaque-->标记是否需要阻止通过该配置创建的代理对象转换为Advised类型,默认值为false,表示代理对象可以被转换为Advised类型
// method.getDeclaringClass().isInterface()-->目标对象是接口
// method.getDeclaringClass().isAssignableFrom(Advised.class)-->
// 是用来判断一个类Class1和另一个类Class2是否相同或者Class1类是不是Class2的父类。例如:Class1.isAssignableFrom(Class2)
else if (!this.advised.opaque
&& method.getDeclaringClass().isInterface()
&& method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 5、解决目标对象内部自我调用无法实施切面增强,在这里暴露代理
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
// 6、获取当前方法的拦截器链,并执行调用
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 检测是否拦截器链是否为空,如果拦截器链为空,那么直接通过反射调用目标对象的方法,避免创建MethodInvocation
// 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.
// 通过反射直接调用目标对象的方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 创建MethodInvocation对象并调用proceed方法,拦截器链被封装到了invocation中
// 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();
}
// 7、返回结果
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null
&& retVal == target
&& returnType != Object.class
&& returnType.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;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
具体的执行过程,注释里已经分析的很清楚了,最核心的就是获取拦截器链和执行拦截器链调用。留在下一章分析。