Spring原理学习系列之三:Spring AOP原理(从源码层面分析)-------上部

简介: 本文是Spring原理分析的第三篇博文,主要阐述Spring AOP相关概念,同时从源码层面分析AOP实现原理。对于AOP原理的理解有利于加深对Spring框架的深入理解。同时我也希望可以探究Spring框架在处理AOP的解决思路,学习框架的时候,有时候需要站在设计者的角度上去考虑,如果自己是设计者遇到同样需要解决的问题自己会怎么去处理,然后再对照实际框架中的处理方式,这样可以发现自己考虑不足之处。本文侧重于找到Spring框架处理AOP的起点,至于涉及到的动态代理相关的问题将在下一篇文中着重介绍。

引言

本文是Spring原理分析的第三篇博文,主要阐述Spring AOP相关概念,同时从源码层面分析AOP实现原理。对于AOP原理的理解有利于加深对Spring框架的深入理解。同时我也希望可以探究Spring框架在处理AOP的解决思路,学习框架的时候,有时候需要站在设计者的角度上去考虑,如果自己是设计者遇到同样需要解决的问题自己会怎么去处理,然后再对照实际框架中的处理方式,这样可以发现自己考虑不足之处。

本文侧重于找到Spring框架处理AOP的起点,至于涉及到的动态代理相关的问题将在下一篇文中着重介绍。

  • 提问题
  • AOP概念
  • AOP代码示例
  • 源码分析
  • 总结


一、提问题

到底什么是AOPSpring框架到底在什么阶段进行数据织入的?AOP在实际项目开发中到底有什么作用以及怎样将AOP的编程思想运用到我们的实际项目开发中?

二、AOP概念

AOP(Aspect Orient Programming),我们通常称为面向切面编程。所谓切面是相对于面向对象来说的。面向对象是将实物抽象为对象,这是个纵向的概念。而面向切面是一个横向的概念,它更加关注那些散落在代码中公用的不涉及具体业务逻辑的通用处理方式,例如日志、权限验证以及统一异常处理等等。它是对于面向对象编程思想的一种结构化补充。核心思想就是将与业务逻辑无关的进行统一的框架织入,不对原有代码以及业务逻辑造成侵入。

Spring AOP在运行时,能够动态地将代码切入到指定的类的指定方法、指定位置上的编程思想就是面向切面编程,这种切入的特点是不影响原来的业务逻辑。但是像AspectJ可以在编译阶段以及类加载阶段进行织入。


一些概念的说明:

  1. 切面

所谓切面,按照自己的理解可以把它看作为一把刀,将他横切于其他物品,通过@Aspect来将类定义一个切面,它就是切点与通知的结合,如下图所示。

image.png

  1. 切点
    本质上来说,就是需要定义一个切入点表达式,使得可以在增强处理中使用到。通过切点定位和筛选特定的连接点。它关注通知需要织入的一个或者多个连接点,切入点包括两部分:
    (1)切入点表达式:指定切入点与哪些方法进行匹配;
    (2)切入点名称:方法签名
  2. 连接点
    连接点是一个相对虚拟的概念,可以将它理解为切点的集合,也就是Spring允许我们进行通知操作的地方,比如方法、异常抛出的地方,如果使用aspectj则也支持在构造器中或者属性中允许通知。
  3. 通知
    通俗地说,通知就是我们需要实现的功能,可以分为前置、后置、异常、最终以及环绕通知这五类。例如日志,权限等业务逻辑。
  • 前置通知:在目标方法或者连接点被调用前执行通知操作;
  • 后置通知:在某些连接点执行完成之后进行通知操作;
  • 异常通知:在方法抛出异常退出当前时进行通知操作;
  • 最终通知:当切入点退出时无论是方法正常执行结束还是异常抛出后退出执行的通知操作;
  • 环绕通知:包围一个切点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。


三、AOP代码示例

1.定义一个普通业务逻辑方法

@Component("userDao")
public class UserDao {
    /**
     * @Description: 查询
     * @Return
     * @Author taomeng
     * @Version V1.0.0
     * @CreateDate: 2018/9/23 11:31
     */
    public void query() {
        System.out.println("query user data!!!");
    }
}

2.定义一个切面类,同时在这个切面类中将切点等进行定义。

/**
 * @Auther: taomeng
 * @Date: 2018/9/17 23:32
 * @Description: 定义切面类
 */
@Configuration
@Aspect
@ComponentScan(basePackages = {"com.tm.springrun.module"})
@ImportResource(locations = {"classpath:spring-context.xml"})
public class AopConfig {
    /**
     * @Description:定义切点
     * @Return
     * @Author taomeng
     * @Version V1.0.0
     * @CreateDate: 2018/9/23 11:32
     */
    @Pointcut("execution(* com.tm.springrun.module.dao.UserDao.query())")
    public void declareJointPointExpression(){}
    @Before("declareJointPointExpression()")
    public  void beforeMethod(JoinPoint joinPoint) {
        String method = joinPoint.getSignature().getName();
        System.out.println("The method of before:" + method);
    }
    @After("declareJointPointExpression()")
    public void afterMethod(JoinPoint joinPoint) {
        String method = joinPoint.getSignature().getName();
        System.out.println("The method of after:" + method );
    }
}

3.在Spring Context中获取Bean的实例,同时执行Bean实例的方法。

/**
 * @Auther: taomeng
 * @Date: 2018/9/17 23:37
 * @Description: 测试AOP
 */
public class Test {
    public static void main(String[] args) {
        //1.加载spring环境
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        //2.获取spring context中的bean的实例
        UserDao userDao = (UserDao)annotationConfigApplicationContext.getBean("userDao");
        //3.执行bean中的方法
        userDao.query();
    }
}

同时需要在配置文件中开启AOP,如下所示:

<aop:aspectj-autoproxy/>


四、源码分析

Spring AOP进行源码分析,就是要找到Spring框架中处理AOP的源头以及在什么阶段进行 数据织入的,带着这样的问题去做源码分析才可以做到有的放矢,要不然那么多源码看下去就像无头苍蝇一样不知道如何下手。

如同第二节示例代码所示, 在方法执行时,通知已经被执行了。那么Spring AOP应该是在前两个步骤中起作用的,要么是在Bean定义时候,要么就是在获取Bean的时候进行的。如下图所示,我们希望通过debug的方式找到AOP在什么阶段开始起作用。

image.png

以下为寻找AOP起作用起点的过程,首先我们猜测Spring在处理AOP的时候是在获取bean的时候进行数据的织入的,所以在获取bean的时候进行断点跟踪。

image.png

进入断点,进入AbstractBeanFactory类中的getBean方法。

image.png

进入getBean方法,在这个方法中

image.png

此时发现对象已经被代理了,所以需要到getSingleton(beanName)这个方法中去查看。

image.png

在这个我们可以发现,Spring框架存放bean名称以及对应的对象的数据结构实际上是一个ConcurrentHashMap。如下所示:

/** Cache of singleton objects: bean name --> bean instance */
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

我们可以看一下在这个map中的存放了Spring中加载的对象,这也是Spring IOC的核心。既然在这个map中获取到了bean,那肯定是在某个地方将对象进行put操作。全文搜索后发现,在DefaultSingletonBeanRegistry这个类中的addSingleton方法中进行对象put操作。

image.png

从上图可知,在对象put操作时,userDao对象已经被代理了。这就说明在获取到bean的时候,对应的对象已经是被代理过的。所以实际数据织入并不是在获取bean的时候进行的,而是在bean加载到Spring环境中的时候就已经完成了。

那接下来我们需要找出调用addSingleton方法的地方,

image.png

从下图可以看出来,此时的mdb还是原生的,那说明此时还没有进行数据织入,是在下面的方法中进行的。

image.png

doCreateBean方法中,我们跟踪到initializeBean的方法,此时发现bean已经被数据织入了,继续进入方法。

image.png

继续查看initializeBean方法内部,如下图所示:

image.png

再继续查看

image.png

再进入方法进行查看

image.png

至此,我们终于跟踪到实现代理的最初部分,其中根据条件判断是jdk代理还是cglib代理。

image.png

五、总结

Spring源码很复杂,如果不是带着目标去看,很难抓住重点。在寻找数据织入的部分,需要一步一步进行,每找到一个部分就需要在对应的部分打上断点,同时去掉之前的断点,不断迭代深入,直到获取到最终的起点,关于代理的这部分内容会放到下一篇文章中进行详细阐述。

根据第三章中的源码分析,我们明确了Spring框架进行数据织入的起点,如下图所示,是在Spring框架加载Bean的时候进行的。

image.png

Spring AOP相关知识树如下图所示:

image.png

相关文章
|
5月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
702 22
|
5月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2009 0
|
5月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
642 0
|
4月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
4月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
576 2
|
6月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
6月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
4月前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
1183 6
AOP 切面编程