【深入理解Spring AOP】核心原理与代理机制详解
深入理解Spring AOP:核心原理与代理机制详解
引言
在现代Java开发中,面向切面编程(AOP)已经成为解决横切关注点的主流方案。作为Spring框架的核心模块之一,Spring AOP通过代理机制实现了强大的切面功能。本文将全面剖析Spring AOP的工作原理,深入讲解两种代理机制的实现细节,并补充实际开发中的最佳实践。
一、AOP基础概念回顾
1.1 什么是AOP
面向切面编程(Aspect-Oriented Programming)是一种通过预编译方式和运行期动态代理实现程序功能统一维护的技术。它是对OOP的补充,专门用于处理分布在应用中多处的功能(称为横切关注点)。
核心价值:
- 分离业务逻辑与系统服务(如日志、事务)
- 提高代码复用性
- 使开发者更专注于业务实现
1.2 AOP核心术语
| 术语 | 说明 |
|---|---|
| 切面(Aspect) | 模块化的横切关注点,包含通知和切点 |
| 连接点(Join Point) | 程序执行过程中的特定点,如方法调用或异常抛出 |
| 通知(Advice) | 在连接点执行的动作,分为前置、后置、返回、异常和环绕五种类型 |
| 切点(Pointcut) | 匹配连接点的谓词,确定哪些连接点会被通知影响 |
| 引入(Introduction) | 为类动态添加方法或字段 |
二、Spring AOP代理机制深度解析
2.1 代理模式基础
代理模式是一种结构型设计模式,Spring AOP基于代理模式实现,主要采用两种技术:
JDK动态代理
- 基于接口实现
- 使用
java.lang.reflect.Proxy创建 - 要求目标类必须实现至少一个接口
CGLIB代理
- 基于子类继承
- 通过修改字节码实现
- 不需要接口支持
- 无法代理final类和方法
2.2 JDK动态代理实现详解
实现原理:
public class JdkProxyDemo {
interface Service {
void serve();
}
static class RealService implements Service {
public void serve() {
System.out.println("实际服务执行");
}
}
static class JdkProxyHandler implements InvocationHandler {
private final Object target;
public JdkProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【JDK代理】前置处理");
Object result = method.invoke(target, args);
System.out.println("【JDK代理】后置处理");
return result;
}
}
public static void main(String[] args) {
Service realService = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new JdkProxyHandler(realService));
proxy.serve();
}
}
关键点分析:
- 通过
Proxy.newProxyInstance创建代理实例 InvocationHandler负责拦截所有方法调用- 代理对象会实现目标接口的所有方法
2.3 CGLIB代理实现详解
实现原理:
public class CglibProxyDemo {
static class RealService {
public void serve() {
System.out.println("实际服务执行");
}
}
static class CglibInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB代理】前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("【CGLIB代理】后置处理");
return result;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback(new CglibInterceptor());
RealService proxy = (RealService) enhancer.create();
proxy.serve();
}
}
关键点分析:
- 使用
Enhancer创建代理类 - 通过
setSuperclass指定目标类 MethodInterceptor处理所有方法调用- 生成的目标类子类字节码
2.4 两种代理对比
| 特性 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现方式 | 反射机制 | 字节码操作 |
| 依赖 | JDK内置 | 需要第三方库 |
| 目标要求 | 必须实现接口 | 普通类即可 |
| 性能 | 创建快,执行慢 | 创建慢,执行快 |
| 方法拦截范围 | 仅接口方法 | 除final方法外的所有方法 |
| 代理类特点 | 实现相同接口 | 目标类的子类 |
三、Spring AOP工作原理补充
3.1 代理创建流程
- Bean初始化阶段:在
AbstractAutowireCapableBeanFactory中完成 - 代理判断:通过
AbstractAutoProxyCreator检查是否需要代理 - 通知获取:收集所有适用的Advisor
- 代理生成:根据配置选择JDK或CGLIB方式
- 代理缓存:生成的代理对象会被缓存复用
3.2 方法调用链
Spring AOP使用责任链模式处理拦截器调用:
客户端调用 → 代理对象 → 拦截器链 → 目标方法
核心实现类ReflectiveMethodInvocation负责维护和执行拦截器链。
3.3 性能优化要点
切点表达式优化:
- 避免使用过于宽泛的表达式(如
execution(* *..*(..))) - 优先使用
@annotation等精确匹配方式
- 避免使用过于宽泛的表达式(如
代理选择策略:
// 强制使用CGLIB代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
缓存利用:
- Spring默认会缓存代理类和切点匹配结果
- 避免在切面中频繁创建新对象
四、高级特性与最佳实践
4.1 解决自调用问题
问题场景:
@Service
public class OrderService {
public void placeOrder() {
this.validate(); // 自调用不会触发AOP
}
@Transactional
public void validate() {
// 事务不会生效
}
}
解决方案:
- 重构代码结构,避免自调用
- 通过AopContext获取当前代理:
((OrderService) AopContext.currentProxy()).validate();
- 使用AspectJ编译时织入
4.2 动态切面配置
Spring允许运行时修改切面配置:
Advised advised = (Advised) applicationContext.getBean("serviceBean");
advised.addAdvice(new MyNewAdvice());
advised.removeAdvice(oldAdvice);
4.3 引入(Introduction)
为对象动态添加接口实现:
@Aspect
public class IntroductionAspect {
@DeclareParents(value="com.example.service.*",
defaultImpl=DefaultLockable.class)
public static Lockable mixin;
}
五、Spring AOP与AspectJ对比
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时 | 编译时/加载时 |
| 功能范围 | 仅方法级别 | 字段、构造器、静态初始化等 |
| 性能影响 | 有运行时开销 | 无运行时开销 |
| 配置复杂度 | 简单 | 较复杂 |
| 适用场景 | 简单切面需求 | 复杂切面需求 |
选型建议:
- 大多数Spring应用使用Spring AOP即可
- 需要拦截非方法操作或追求极致性能时选择AspectJ
六、常见问题排查
代理不生效检查清单:
- 确保目标Bean由Spring管理
- 检查切点表达式是否匹配
- 确认方法调用是通过代理对象
- 检查是否有多个代理互相覆盖
代理类型检查工具:
AopUtils.isAopProxy(bean); // 是否代理对象
AopUtils.isCglibProxy(bean); // 是否CGLIB代理
AopUtils.isJdkDynamicProxy(bean);// 是否JDK代理
获取原始目标对象:
if (AopUtils.isAopProxy(bean)) {
Object target = ((Advised) bean).getTargetSource().getTarget();
}
结语
Spring AOP通过巧妙的代理机制实现了强大的切面编程能力。理解其底层原理对于正确使用和问题排查至关重要。在实际项目中,建议:
- 根据具体场景选择合适的代理方式
- 遵循"单一职责"原则设计切面
- 注意性能敏感场景的优化
- 合理利用Spring的调试工具进行问题诊断
希望本文能帮助你深入理解Spring AOP的代理机制,在项目中更加得心应手地使用AOP解决横切关注点问题。
【深入理解Spring AOP】核心原理与代理机制详解的更多相关文章
- 【转】java的动态代理机制详解
java的动态代理机制详解 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们 ...
- Java 反射 设计模式 动态代理机制详解 [ 转载 ]
Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...
- java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
- Spring AOP实现原理-动态代理
目录 代理模式 静态代理 动态代理 代理模式 我们知道,Spring AOP的主要作用就是不通过修改源代码的方式.将非核心功能代码织入来实现对方法的增强.那么Spring AOP的底层如何实现对方法的 ...
- Spring AOP四种实现方式Demo详解与相关知识探究
一.前言 在网络上看到一篇博客Spring实现AOP的4种方式,博主写的很通俗易懂,但排版实在抓狂,对于我这么一个对排版.代码格式有强迫症的人来说,实在是不能忍受~~~~(>_<)~~~~ ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- java的动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java的动态代理机制详解(转)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- [转载] java的动态代理机制详解
转载自http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代 ...
- ava的动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
随机推荐
- PVE虚拟机安装详解
前言 PVE,全称Proxmox Virtual Environment,是基于Debian的Linux系统,虚拟机内核为KVM.硬件兼容性优秀.界面功能不强,很多操作要靠命令行,但扩展能力几乎是无限 ...
- ingress配置https报错certificate.lua:259: call(): failed to set DER private key: d2i_PrivateKey_bio() failed, context: ssl_certificate_by_lua*
困扰我2天的报错问题:certificate.lua:259: call(): failed to set DER private key: d2i_PrivateKey_bio() failed, ...
- 初识for循环
1.格式 想象成跑3圈,初始化语句是i=1,即第一圈,判断条件就是i<=3,跑完三圈就停,条件控制语句就是i++,跑完一圈就要在心里加1圈. . 2.for循环的执行流程 1.执行初始化语句,在 ...
- panic: qtls.ConnectionState not compatible with tls.ConnectionState
问题重现 之前一个 go-micro 1.18 的项目 执行 go run main.go 命令时出现以下报错 go run main.go panic: qtls.ConnectionState n ...
- 活动中台系统慢 SQL 治理实践
作者:vivo 互联网服务器团队- Zhang Mengtao 活动中台系统作为中台项目非常注重系统性能和用户体验,数据库系统性能问题会对应用程序的性能和用户体验产生负面影响.慢查询可能导致应用程序响 ...
- 推荐IT公司历史精品书籍
浪潮之巅,吴军注 讲述了各大it公司发展历程,从兴起,到转折或衰落,蓝图宏伟,对现在的各大公司和技术发展的理解有一些帮助. 包含AT & T,IBM,微软,苹果,亚马逊等公司.
- public synchronized boolean add(E e)方法源码
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, C ...
- CF1627B题解
Not Sitting 题面翻译 Rahul 和 Tina 在玩一个游戏.游戏在一个 n×mn\times mn×m 的网格图上进行,记第 rrr 行第 ccc 列上的格子为 (r,c)(r,c)(r ...
- 三维装箱问题(3D Bin Packing Problem, 3D-BPP)
提出问题 集装箱海运家具, 沙发, 茶几, 椅子等等, 有多少套家具,以及每个家具的长宽高都会告诉你. 把所有的家具都装进集装箱里, 要求通过算法算出一共需要多少集装箱. 1.要考虑怎样装, 需要的集 ...
- Graphpad Prism10.1.2 中文版科学绘图软件 安装包下载
Prism10中文版下载链接: https://pan.baidu.com/s/18a0_uLi3ANWC3KxlHOzZAA?pwd=6666 提取码: 6666 Graphpad Prism 是一 ...