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

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



目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
22天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
41 2
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
67 9
|
2月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
53 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
2月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
168 5
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
244 2
|
2天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
9天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
52 14
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
49 1
SpringBoot入门(7)- 配置热部署devtools工具