Spring原理揭秘--初识AOP
我们知道软件开发一直在追求高效,易维护,易扩展的特性方式。在面向过程编程到面向对象编程的历程中,程序的开发有了非常大的进步。但是oop的方式缺依然存在着一些缺点。oop的方式可以将业务进行很好的分解和封装使其模块化,但是却没办法更好的避免类似于系统需求的视线在系统中各处散落这样的问题。比如:不同的主业务可能都会需要一些与业务无关的逻辑代码--日志记录,事务处理等。那么这些相同的无关逻辑在不同业务中的对应关系则会变成1:n的方式。这样就会造成后续维护的成本贼高。因此推出了AOP的方式来对oop的缺点进行优化。
AOP被称为面向方面编程或者切面编程。相当于一个刀直接在业务逻辑上横切。只需要实现AOP的逻辑即可插入到任何的业务逻辑中去从而只需要维护AOP逻辑即可管理所有的非业务逻辑。AOP和OOP是互补的方式而不是竞争的方式。
Java平台上的AOP实现机制
而在java平台上实现AOP的方式有以下几种
动态代理:
JDK1.3之后引入了动态代理的机制,可以在运行期间,为相应的接口动态生成代理对象。所以,我们可以将横切关注点逻辑封装到动态代理的InvocationHandler当中,然后在运行期间通过动态代理生成对应对象的子类将横切逻辑织入到代理对象当中。这种方式相比于静态代理则是性能稍微逊色一些。同时有一个缺点就是只能实现接口的动态代理,那些没有接口实现的类则无法实现动态代理
动态字节码增强:
java虚拟机加载的class文件都是符合一定的规范的,所以只要交给java虚拟机运行的class文件符合java class规范那么程序运行就没有什么问题。而通常情况下class文件是通过java编译期编译而形成的。而java则推出了CGLIB的方式等java工具库,在运行期间通过修改字节码的方式来实现动态增强,将新增的业务逻辑加入到AOP逻辑当中。CJLIB的方式则不需要java类去继承接口,只需要java类能够生成子类即可就能实现动态字节码增强
AOP的各个组件
Joinpoint:
在系统运行之前,aop的功能就需要织入到OOP当中,所以这个注入的过程我们需要知道在OOP代码中哪些执行点上进行织入操作,这些将要在其之上进行织入操作的系统执行点就是Joinpoint
比如上述的代码执行流程图当中我们可以在以下阶段进行织入:
方法调用:当某个方法被调用的时候所处的程序执行点,图中的三个圆圈则是属于这种类型。
方法调用执行:这种方式的本质是在执行,也就是在方法体内执行的时候才会调用。与方法调用的区别在于一般方法调用先执行而方法调用执行后执行
构造方法调用:与方法调用意思几乎相同,在构造方法进行初始化的时候进行执行
构造方法调用执行:则是在执行构造方法的时候才会真正的执行
字段设置:在设置某个属性通过setter方法被设置或者直接被设置的时点。该Joinpoint的本质是对象属性被设置时候触发
字段获取:在获取相对字段的时候也就是通过getter方法访问或者直接访问的时候则会触发
异常处理执行:当出现异常的时候,则会在对应的异常抛出点执行
类初始化:类初始化则是类中某些静态类型或者静态代码块初始化的时间点上的时候会执行
基本上在代码执行的过程中所有的执行时点上都可以作为Joinpoint,那么这些joinpoint有了如果我们仅仅在需要的地方执行如何告诉程序呢?
Poincut
Poincut则是告诉程序需要在哪些Joinpoint上去执行AOP逻辑处理,如果说Joinpoint是所有的可执行点,那么Poincut则是真正需要执行AOP的执行点。
在 Spring AOP 中,Pointcut 主要通过以下方式实现:
1. 基于注解的 Pointcut
使用 @Pointcut
注解定义切入点表达式,常见的表达式类型包括:
- execution:匹配方法执行连接点。
- within:匹配指定类型内的方法。
- @annotation:匹配带有特定注解的方法。
2. execution 表达式
最常用的表达式类型,语法为:
execution(修饰符? 返回类型 类路径?方法名(参数) throws 异常?)
2. 其他表达式类型
within:限制匹配范围为特定类型。
// 匹配 UserService 类中的所有方法
within(com.example.service.UserService)// 匹配 service 包下的所有类的方法
within(com.example.service.*)// 匹配 service 包及其子包下的所有类的方法
within(com.example.service..*)
@annotation:匹配带有特定注解的方法。
// 匹配所有带 @Loggable 注解的方法
@annotation(com.example.annotation.Loggable)
2. 基于 XML 配置的 Pointcut
在 Spring XML 配置中,可以使用 <aop:pointcut>
元素定义切入点
Pointcut 本身只是定义筛选条件,需要与通知(Advice) 结合才能实现 AOP 的增强功能
💡 黄金法则:
Pointcut = 在哪里切(Where) + 切什么(What)
好的切点表达式应该像精准的手术刀,而非大锤。
Adivce
Advice(通知) 是核心概念之一,用于定义在目标方法执行的特定时机插入的横切逻辑(如日志、事务、权限校验等)。它决定了切面(Aspect)行为的具体执行位置和内容。
以下是Spring AOP支持的Advice类型及其执行时机:
通知类型 | 注解 | 执行时机 |
---|---|---|
Before Advice | @Before | 在目标方法执行前触发(例如:权限校验) |
After Returning | @AfterReturning | 在目标方法成功返回后触发(例如:记录操作结果) |
After Throwing | @AfterThrowing | 在目标方法抛出异常后触发(例如:异常日志记录) |
After (Finally) | @After | 在目标方法结束后触发(无论成功/异常,类似finally 块,例如:资源清理) |
Around Advice | @Around | 环绕目标方法执行,可控制方法调用、修改参数/返回值(例如:性能监控、事务管理) |
Advice 是 Spring AOP 实现横切逻辑的核心机制,通过五种类型覆盖方法执行的各个生命周期节点,显著提升代码复用性和可维护性。正确使用Advice能高效解决日志、事务、安全等横切关注点问题,是Spring生态中不可或缺的组件。
织入和织入器
织入 是指将 切面(Aspect) 中定义的横切逻辑(Advice)动态植入到目标对象(Target Object) 的过程。类比织布,就是将切面的"线"编织到业务逻辑的"布料"中。
关键作用:
动态增强目标对象
在目标方法执行的特定位置(如方法调用前、返回后、异常时)插入横切逻辑(如日志、事务等)。实现逻辑解耦
将核心业务逻辑(如用户服务)与非功能性需求(如日志记录)分离,通过织入机制在运行时组合。支持多种植入方式
织入时机 | 实现方式 | 特点 |
---|---|---|
编译时织入 | 使用 AspectJ 编译器 | 性能高,但需要特殊编译步骤(如 Maven 插件) |
类加载时织入 | 通过 Java Agent(LTW) | 无需修改源码,在 JVM 加载类时植入(Spring 支持) |
运行时织入 | Spring AOP 默认方式(动态代理) | 无需编译支持,但仅作用于 Spring 容器管理的 Bean(最常用) |
织入器 是负责执行织入操作的底层引擎。在 Spring AOP 中,织入器由框架内部实现,开发者通过配置驱动其工作。
核心职责:
解析切面配置
读取@Aspect
注解或 XML 配置,识别 Pointcut(切入点)和 Advice(通知)的定义。匹配连接点(Join Point)
根据 Pointcut 表达式(如execution(* com.service.*.*(..))
)定位需要增强的目标方法。生成代理对象
代理类型 触发条件 实现方式 JDK 动态代理 目标类实现了接口 基于 java.lang.reflect.Proxy
CGLIB 代理 目标类未实现接口(或强制使用) 通过字节码生成子类(如 UserService$$EnhancerBySpringCGLIB
)植入 Advice 逻辑
在代理对象中按顺序集成各类通知(如@Before
、@Around
),构建执行链。