Spring5源码(37)-SpringAop代理调用过程(一)

简介: Spring5源码(37)-SpringAop代理调用过程(一)


经过前两篇的分析,已经成功创建了目标类的代理,接着分析代理的调用过程。在前面的章节已经介绍过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);
    }
}

具体的排序方法不做过深入的分析了,但是其排序的原则和增强的执行顺序还是要简单介绍一下:

  1. 如果在一个切面中没有相同类型的增强(例如:一个切面类里不同时有两个前置增强),且目标方法里没有异常抛出,那么其执行顺序是:环绕增强 --> 前置增强 --> 目标类方法 --> 前置增强 --> 后置最终增强 --> 后置返回增强
  2. 如果在一个切面中没有相同类型的增强(例如:一个切面类里不同时有两个前置增强),但目标方法里存在异常,那么其执行顺序是 :环绕增强 --> 前置增强 --> 目标类方法(直至调用到发生异常的代码)--> 后置最终增强--> 后置异常增强
  3. 如果两条相同增强类型增强来自同一切面,它们将具有相同的顺序。然后根据以下规则进一步排序。如果是后置增强,那么最后声明的增强将获得最高的优先级,对于其他类型的增强,首先声明的增强将获得最高的优先级

其执行顺序已经了解了,下面通过两个实例才验证一下(另外在前面的文章介绍稍微有些缺陷,这里也修正一下)。

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);
        }
    }
}

具体的执行过程,注释里已经分析的很清楚了,最核心的就是获取拦截器链和执行拦截器链调用。留在下一章分析



目录
相关文章
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
504 70
|
9月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
228 0
|
11月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
405 7
|
5月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
904 0
|
6月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
646 0
|
2月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
297 3
|
2月前
|
Java 测试技术 数据库连接
【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
846 2