Spring 源码学习(八) AOP 使用和实现原理(三)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 我们在业务开发中,使用得最多的是面向对象编程(OOP),因为它的代码逻辑直观,从上往下就能查看完整的执行链路。 在这个基础上延伸,出现了面向切面编程(AOP),将可以重复性的横切逻辑抽取到统一的模块中。 例如日志打印、安全监测,如果按照 OOP 的思想,在每个方法的前后都要加上重复的代码,之后要修改的话,更改的地方就会太多,导致不好维护。所以出现了 AOP 编程, AOP 所关注的方向是横向的,不同于 OOP 的纵向。 所以接下来一起来学习 AOP 是如何使用以及 Spring 容器里面的处理逻辑~

创建代理

通过前面的流程,获取到了所有对应 bean 的增强器后,可以开始代理的创建。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
            @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    ProxyFactory proxyFactory = new ProxyFactory();
    // 拷贝,获取当前类中的相关属性
    proxyFactory.copyFrom(this);
    // 决定对于给定 bean 是否应该使用 targetClass 而不是他的接口代理
    if (!proxyFactory.isProxyTargetClass()) {
        // 检查 proxyTargetClass 设置以及 preserveTargetClass 属性
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            // 添加代理接口
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    // 这一步中,主要将拦截器封装为增强器
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    // 定制代理
    customizeProxyFactory(proxyFactory);
    // 用来控制代理工厂被配置之后,是否含允许修改通知
    // 缺省值为 false,不允许修改代理的配置
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
    // 生成代理,委托给了 ProxyFactory 去处理。
    return proxyFactory.getProxy(getProxyClassLoader());
}

对于代理类的创建和处理, Spring 委托给了 ProxyFactory 去处理,在上面贴出的函数主要是对 ProxyFactory 的初始化操作,进而对真正的创建代理做准备,主要流程如下:

  1. 获取当前类的属性
  2. 添加代理接口
  3. 封装 Advisor 并加入到 ProxyFactory
  4. 设置要代理的类
  5. 为子类提供定制的函数 customizeProxyFactory,子类通过该方法对 ProxyFactory 进行进一步的封装
  6. 进行获取代理操作

比较关键的是第三个步骤和第六个步骤,其中在第三个步骤中,进行的是拦截器包装,详细代码流程请查 [注释 8.9 为给定的bean创建AOP代理] 和 [注释 8.10 包装拦截器,封装成 Advisor]

接着,完成了所有增强器的封装过程,到了解析的最后一步,进行代理的创建和获取

public Object getProxy(@Nullable ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

创建代理 createAopProxy()

定位到创建代理的代码:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

从上面代码中能看出,根据了几个关键属性,判断创建的是哪种类型的 AopProxy,一种是 JDK 动态代理,另一种是 CGLIB 动态代理。

前面提到过的 proxy-target-class 属性和 targetClass 属性,在这里判断了应该创建哪一个代理。


获取代理 getProxy()

2.jpg

观察图片以及前面分析,可以知道有两种代理方式:[JDK 动态代理] 和 [CGLIB 动态代理]

同时先说下动态代理的含义:抽象类在编译期间是未确定具体实现子类,在运行时才生成最终对象。


JDK 动态代理

JDK 代理是默认推荐的代理方式,使用的是 Proxy + InvocationHandler

可以通过以下方式实现:定义一个接口、实现类,和一个处理器继承于 InvocationHandler,然后重载处理器中的 invoke 方法,对代理对象进行增强。

JdkDynamicAopProxy.java

public Object getProxy(@Nullable ClassLoader classLoader) {
    // 注释 8.11 JDK 动态代理
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

获取代理的核心步骤在 Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),第三个参数是 JdkDynamicAopProxy 本身,而且它实现了 InvocationHandler 接口,重载了 invoke 方法。

org.springframework.aop.framework.JdkDynamicAopProxy#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 注释 8.12 jdk 动态代理重载的 invoke 方法
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
        Object retVal;
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // Get the interception chain for this method.
        // 获取此方法的拦截链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        // 检查我们是否有任何切面逻辑。如果我们不这样做,我们可以回退直接反射调用目标,并避免创建 MethodInvocation。
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // 将拦截器封装在 ReflectiveMethodInvocation,便于使用 proceed 执行拦截器
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 执行拦截器链
            retVal = invocation.proceed();
        }
        ...
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

创建 JDK 代理过程中,主要的工作时创建了一个拦截器链,并使用 ReflectiveMethodInvocation 类进行封装,封装之后,逐一调用它的 proceed 方法, 用来实现在目标方法的前置增强和后置增强。

org.springframework.aop.framework.ReflectiveMethodInvocation#proceed

public Object proceed() throws Throwable {
    // 执行完所有增强器后执行切点方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 获取下一个要执行的拦截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 动态匹配
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 匹配失败,跳过拦截器,直接返回
            return proceed();
        }
    }
    else {
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

具体代码和注释请定位到该方法查看。关于 JDK 动态代理,深入学习的话也可以单独拎出来,所以推荐看这篇资料 小豹子带你看源码:JDK 动态代理,进行了和学习

CGLIB 动态代理

CGLIB[Code Generation LIB] 是一个强大的高性能的代码生成包。它广泛应用于许多 AOP 框架。

再次推荐参考资料一,这位老哥将 CGLIB 代理, 详细介绍了 CGLIB 在什么场景使用,以及被它增强后代码处理顺序,Cglib及其基本使用。

希望看完这篇文章,能过了解到 CGLIB 代码生成包具体是如何对类进行增强。


代理增强结果

通过前面一系列步骤,解析标签、属性、增强方法,到最后获取 CGLIB 代理,通过代理创建 bean

来看下最后被代理的 bean 内部:3.jpg

从图中可以看到,最终创建的是被修饰后的 bean,内部很明显是 CGGLIB 代理生成的代码,我们在不修改业务代码的情况下,实现了方法增强。


静态 AOP

既然有动态代理,那么也会有静态代理。

使用静态 AOP 的时候,需要用到 LTW (Load-Time Weaving 加载时织入),指的是在虚拟机载入字节码文件时动态织入 AspectJ 切面。

AOP 的静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对目标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理调用的过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是在启动时便完成了字节码增减,当系统再次调用目标类时,与调动正常的类并无区别,所以在效率上会相对高些。

关于静态 AOP 的使用和学习,可以参考这篇文章:从代理机制到Spring AOP


总结

动态 AOP 使用起来很简单,对于如何实现,总结起来就两点:

  1. 动态解析 `AOP` 标签
  2. 创建 `AOP` 代理

但在 Spring 底层实现逻辑却是复杂到不行,从 Spring 框架中可以看到这是良好的代码设计思路,顶层入口尽量简单,使用者很容易就能掌握该功能,复杂实现逻辑都被隐藏了。

写这一篇 AOP 学习总结,花了将近一周,先看了一遍书籍, 下班后花了一晚,将大致流程理了一遍,第二天晚上走读代码,发现有些地方还存在疑惑,例如 JDKcglib 动态代理是怎么回事,翻阅查询资料,弄懂后又过了一天。

将代码注释加上,分析动态代理每一个步骤做的事情,结合之前学的后处理器 BeanPostProcessor 知识和自定义标签解析知识一起又梳理一遍。零零散散,终于整理完成。

在静态 AOP 知识点,按照我的理解,越往系统底层深入,它的执行效率越高,所以减少了动态创建代理类和代理目标对象的步骤,静态代理的速度会得到提升。同时由于接近底层后,代码编写的复杂度同样会增加,所以我在权衡高频率使用场景(动态代理),本次学习没有详细去了解,留下这个坑,以后有机会再填吧~


由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正

Gitee 地址 https://gitee.com/vip-augus/spring-analysis-note.git

Github 地址 https://github.com/Vip-Augus/spring-analysis-note

相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
129 6
|
1月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
116 25
|
1月前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
87 24
|
2月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
97 8
|
3月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
225 14
|
4月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
118 5
|
4月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
129 8
|
4月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
4月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
67 5
|
4月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
86 4