为什么一个 @Transactional 注解就能开启事务?揭秘 Spring AOP 的底层魔法

图片

你是否也曾深陷在各种“额外”逻辑的泥潭,为了给一个核心业务方法增加日志、权限校验或缓存,而不得不将这些非核心代码硬塞进业务类中,导致代码臃肿、职责不清?是时候用代理设计模式 (Proxy Design Pattern) 来解脱了!这是一种结构型设计模式,它能为你提供一个对象的替代品或占位符,以控制对这个对象的访问。

在 Spring Boot 中,这个模式是其整个AOP(面向切面编程)框架的基石,也是实现 @Transactional@Cacheable 等神奇注解的底层秘密。它能将你的核心业务逻辑与横切关注点(如事务、安全)优雅地分离。本文将探讨为什么直接访问对象会带来问题,通过一个实际的权限校验示例来展示代理模式的强大威力,并一步步揭示它在 Spring Boot 中的核心地位 —— 让我们今天就开始解锁 Spring AOP 的底层魔法吧!

什么是代理设计模式?🤔

代理模式的核心思想是:**为其他对象提供一种代理以控制对这个对象的访问。**这个代理对象在客户端和目标对象之间起到中介的作用,它可以在将请求传递给真实对象之前或之后,执行一些附加操作。

想象一下你点外卖,你(客户端)通过外卖App(代理)下单,App会帮你处理支付、联系骑手等事宜,最后才通知餐厅(真实对象)做饭。App就是餐厅的代理。

这个模式的核心组件通常包括:

  • • 抽象主题 (Subject): 定义了真实主题和代理主题的公共接口,这样在任何使用真实主题的地方都可以使用代理主题。

  • • 真实主题 (Real Subject): 定义了代理所代表的真实实体,是最终执行业务逻辑的对象。

  • • 代理 (Proxy): 保存一个引用使得代理可以访问实体,并实现了抽象主题接口,这样代理就可以替代实体。它可以在调用真实主题前后执行预处理和后处理操作。

常见的代理类型有:

  • • 保护代理 (Protection Proxy): 控制对真实对象的访问权限。

  • • 虚拟代理 (Virtual Proxy): 延迟创建昂贵的对象,直到真正需要它的时候。

  • • 远程代理 (Remote Proxy): 为一个位于不同地址空间的对象提供一个本地的代表。

  • • 缓存代理 (Caching Proxy): 为开销大的运算结果提供临时存储。

为什么要在 Spring Boot 中使用代理模式?💡

代理模式能带来诸多好处:

  • • 控制访问 (Access Control): 核心价值。代理可以作为“守门员”,在客户端访问真实对象之前进行权限检查、状态验证等。

  • • 增强功能 (Enhancement): 可以在不修改真实对象代码的前提下,为其增加额外的功能,如日志记录、性能监控、事务管理、缓存等。

  • • 延迟加载 (Lazy Initialization): 当一个对象的创建成本很高时,虚拟代理可以等到该对象第一次被真正使用时才去创建它,从而优化应用启动速度和资源消耗。

  • • 解耦与单一职责 (Decoupling & SRP): 将附加功能(如缓存、日志)从核心业务逻辑中分离出来,让真实主题类更纯粹,只专注于业务,符合单一职责原则。

  • • Spring AOP 的基石 (Foundation of Spring AOP): 这是在Spring中使用代理模式最重要的原因。 Spring的AOP就是通过动态代理(JDK动态代理或CGLIB)实现的。当你使用@Transactional@Async@Cacheable或自定义切面时,Spring会自动为你创建一个代理对象来包裹你的Bean,并将相应的增强逻辑织入其中。

问题所在:混乱的非核心逻辑

假设你有一个订单服务,其中的一个方法需要进行权限校验。

你可能会忍不住这样写:

public classOrderServiceImplimplementsOrderService {@OverridepublicvoidcreateOrder(User user, Order order) {// 1. 权限校验逻辑硬编码在业务方法中if (!"ADMIN".equals(user.getRole())) {thrownewSecurityException("只有管理员才能创建订单!");}// 2. 核心业务逻辑System.out.println("订单创建成功!");// ... 保存订单到数据库}
}

这种代码的问题在于:

❌ 违反单一职责原则: OrderService 不仅要负责订单业务,还要负责权限校验。
❌ 代码重复: 如果其他方法也需要同样的权限校验,你就必须复制粘贴这段if代码。
❌ 维护困难: 如果权限逻辑发生变化(例如,VIP用户也可以创建订单),你需要修改所有相关的业务方法。

✅ 代理模式来修复
我们可以创建一个 OrderServiceProxy,它和真实的 OrderServiceImpl 实现同一个接口。客户端通过代理访问,代理在调用真实方法前,先完成权限校验。

一步步实现 Java 示例:图像查看器权限代理

第一步:定义抽象主题接口

public interface ImageViewer {void viewImage(String imageName);
}

第二步:创建真实主题

public class RealImageViewer implements ImageViewer {@Overridepublic void viewImage(String imageName) {System.out.println("正在显示图片: " + imageName);}
}

第三步:创建保护代理

public classSecureImageViewerProxyimplementsImageViewer {private ImageViewer realViewer;private User user;publicSecureImageViewerProxy(User user) {this.realViewer = newRealImageViewer();this.user = user;}@OverridepublicvoidviewImage(String imageName) {// 在调用真实对象前,执行权限检查if (user.hasViewPermission()) {System.out.println("【代理】权限校验通过。");realViewer.viewImage(imageName);} else {System.out.println("【代理】抱歉," + user.getName() + ",你没有权限查看图片。");}}
}
// User类和hasViewPermission()方法此处省略

第四步:客户端使用

public classMain {publicstaticvoidmain(String[] args) {Useradmin=newUser("Admin", true);Userguest=newUser("Guest", false);ImageViewerviewerForAdmin=newSecureImageViewerProxy(admin);viewerForAdmin.viewImage("secret.jpg"); // 可以查看ImageViewerviewerForGuest=newSecureImageViewerProxy(guest);viewerForGuest.viewImage("secret.jpg"); // 没有权限}
}
Spring Boot 应用案例:揭秘@Transactional的魔法

在 Spring Boot 中,我们几乎从不手动创建代理。框架为我们代劳了一切。让我们看看 @Transactional 是如何工作的。

第一步:定义一个普通的业务服务(真实主题)

import org.springframework.stereotype.Service;publicinterfaceUserService {voidcreateUser(String name);
}@Service
publicclassUserServiceImplimplementsUserService {@OverridepublicvoidcreateUser(String name) {System.out.println("正在将用户 " + name + " 保存到数据库...(核心业务逻辑)");// 此处没有一行事务相关的代码!}
}

第二步:通过注解“请求”代理功能
我们只需要在需要事务的方法上,加上 @Transactional 注解。

@Service
public class UserServiceImpl implements UserService {@Override@Transactional // 请求Spring为这个方法提供事务管理public void createUser(String name) {System.out.println("正在将用户 " + name + " 保存到数据库...(核心业务逻辑)");// 如果这里发生异常,事务会自动回滚}
}

第三步:在客户端中注入并验证

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
publicclassAppRunnerimplementsCommandLineRunner {privatefinal UserService userService;publicAppRunner(UserService userService) {this.userService = userService;}@Overridepublicvoidrun(String... args)throws Exception {System.out.println("注入的UserService类型: " + userService.getClass().getName());userService.createUser("Alice");}
}

启动应用,查看控制台输出:

注入的UserService类型: com.example.proxy.UserServiceImpl$$SpringCGLIB$$0
正在将用户 Alice 保存到数据库...(核心业务逻辑)

揭秘时刻:
userService.getClass().getName() 的输出不是 UserServiceImpl,而是一个带有 $$SpringCGLIB$$ 后缀的类。这证明了Spring容器注入给我们的,根本不是原始的 UserServiceImpl 对象,而是由Spring在运行时动态创建的一个代理对象

正是这个代理对象,在调用我们真正的 createUser 方法之前,开启了数据库事务;在方法执行之后,提交或回滚了事务。这就是代理模式在Spring中的威力。

代理模式 vs. 装饰器模式
  • • 意图不同: 代理模式的核心是控制对对象的访问,它可以决定是否将请求转发给真实对象。装饰器模式的核心是增强对象的功能,它一定会执行真实对象的方法,并在此基础上增加新职责。

  • • 关注点: 代理模式关注的是“访问控制”和“隐藏”。装饰器模式关注的是“功能叠加”。

✅ 何时使用代理模式
  • • 当你想为一个对象提供一个替代品或占位符,以控制对它的访问时(如懒加载、权限控制)。

  • • 当你想在不修改对象代码的前提下,为其增加一些通用的、横切性的功能时(如日志、事务、缓存)。

  • • 在Spring中: 当你使用AOP相关的所有功能时,你其实都在隐式地使用代理模式。

🚫 何时不宜使用代理模式
  • • 当一个调用关系非常简单,不需要任何形式的访问控制或功能增强时,引入代理会增加不必要的复杂性。

  • • 当对性能要求极高,无法承受代理带来的微小性能开销时(通常可以忽略不计)。

🏁 总结

代理设计模式是面向对象编程中一个极其重要和强大的模式。它通过引入一个“替身”或“中介”,优雅地实现了对真实对象的访问控制和功能增强,是实现系统解耦和职责分离的关键。

在现代化的 Spring Boot 开发中,代理模式已经不再需要我们手动编写,而是升华为框架的核心基石。Spring AOP 通过动态代理技术,将开发者从繁琐的事务管理、日志记录等横切关注点中解放出来,让我们只需一个简单的注解,就能享受到代理模式带来的巨大威力。这使得我们的系统:

  • • 业务逻辑更纯粹

  • • 代码更简洁,配置化更强

  • • 易于维护和测试

理解代理模式的本质,就是理解Spring AOP核心魔法的关键。掌握它,你才能真正洞悉Spring框架的优雅设计,并编写出更高质量的企业级应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914141.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Spring 中上下文传递的那些事儿》Part 8:构建统一上下文框架设计与实现(实战篇)

📝 Part 8:构建统一上下文框架设计与实现(实战篇) 在实际项目中,我们往往需要处理多种上下文来源,例如: Web 请求上下文(RequestContextHolder)日志追踪上下文&#xf…

配置驱动开发:初探零代码构建嵌入式软件配置工具

前言在嵌入式软件开发中,硬件初始化与寄存器配置长期依赖人工编写重复代码。以STM32外设初始化为例,开发者需手动完成时钟使能、引脚模式设置、参数配置等步骤,不仅耗时易错(如位掩码写反、模式枚举值混淆)&#xff0c…

Elasticsearch混合搜索深度解析(下):执行机制与完整流程

引言 在上篇中,我们发现了KNN结果通过SubSearch机制被保留的关键事实。本篇将继续深入分析混合搜索的执行机制,揭示完整的处理流程,并解答之前的所有疑惑。 深入源码分析 1. SubSearch的执行机制 1.1 KnnScoreDocQueryBuilder的实现 KNN结果被…

Apache HTTP Server 从安装到配置

一、Apache 是什么?Apache(全称 Apache HTTP Server)是当前最流行的开源Web服务器软件之一,由Apache软件基金会维护。它以稳定性高、模块化设计和灵活的配置著称,支持Linux、Windows等多平台,是搭建个人博客…

php中调用对象的方法可以使用array($object, ‘methodName‘)?

是的,在PHP中,array($object, methodName) 是一种标准的回调语法,用于表示“调用某个对象的特定方法”。这种语法可以被许多函数(如 call_user_func()、call_user_func_array()、usort() 等)识别并执行。 语法原理 在P…

【设计模式】单例模式 饿汉式单例与懒汉式单例

单例模式(Singleton Pattern)详解一、单例模式简介 单例模式(Singleton Pattern) 是一种 创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。(对象创建型模式&…

vue3 el-table 行数据沾满格自动换行

在使用 Vue 3 结合 Element Plus 的 <el-table> 组件时&#xff0c;如果你希望当表格中的行数据文本过长时能够自动换行&#xff0c;而不是溢出到其他单元格或简单地截断&#xff0c;你可以通过以下几种方式来实现&#xff1a;方法 1&#xff1a;使用 CSS最简单的方法是通…

windows电脑远程win系统服务器上的wsl2

情况 我自己使用win11笔记本电脑&#xff0c;想要远程win11服务器上的wsl2 我这里只有服务器安装了wsl2&#xff0c;win11笔记本没有安装 因此下面提到的Ubuntu终端指的是win服务器上的wsl2终端 一定要区分是在哪里输入命令&#xff01;&#xff01; 安装SSH 在服务器上&#x…

神经辐射场 (NeRF):重构三维世界的AI新视角

神经辐射场 (NeRF)&#xff1a;重构三维世界的AI新视角 旧金山蜿蜒起伏的街道上&#xff0c;一辆装备12个摄像头的Waymo自动驾驶测试车缓缓驶过。它记录的280万张街景图像并未被简单地拼接成平面地图&#xff0c;而是被输入一个名为Block-NeRF的神经网络。数周后&#xff0c;一…

Kubernetes自动扩缩容方案对比与实践指南

Kubernetes自动扩缩容方案对比与实践指南 随着微服务架构和容器化的广泛采用&#xff0c;Kubernetes 自动扩缩容&#xff08;Autoscaling&#xff09;成为保障生产环境性能稳定与资源高效利用的关键技术。面对水平 Pod 扩缩容、垂直资源调整、集群节点扩缩容以及事件驱动扩缩容…

【CVPR2025】计算机视觉|SIREN: 元学习赋能!突破INR高分辨率图像分类难题

论文地址&#xff1a;https://arxiv.org/pdf/2503.18123v1 代码地址&#xff1a;https://github.com/SanderGielisse/MWT 关注UP CV缝合怪&#xff0c;分享最计算机视觉新即插即用模块&#xff0c;并提供配套的论文资料与代码。 https://space.bilibili.com/473764881 摘要 …

牛客周赛 Round 99

赛时成绩如下&#xff1a;A. Round 99题目描述 对于给定的五位整数&#xff0c;检查其中是否含有数字 99&#xff1b;换句话说&#xff0c;检查是否存在相邻的两个数位&#xff0c;其值均为 。解题思路&#xff1a; 检查相邻的两个数字是否均为9#include <bits/stdc.h> u…

从0到1搭建个人技术博客:用GitHub Pages+Hexo实现

一、为什么要搭建个人技术博客&#xff1f; 在技术圈&#xff0c;拥有个人博客的好处不言而喻&#xff1a; 简历加分项&#xff1a;面试官更青睐有技术沉淀的候选人知识系统化&#xff1a;输出倒逼输入&#xff0c;加深技术理解人脉拓展&#xff1a;吸引同行关注&#xff0c;…

Ubuntu22.04 设置显示存在双屏却无法双屏显示

文章目录一、背景描述二、解决方法一、背景描述 回到工位后&#xff0c;发现昨天离开时还可正常显示的双屏&#xff0c;今早ubuntu22.04 的设置界面显示有双屏&#xff0c;但外接的显示屏无法正常显示。 首先&#xff0c;查看当前图像处理显卡是否为N卡&#xff0c;没错&#…

高亚科技签约奕源金属,助力打造高效智能化采购管理体系

深圳市奕源金属制品有限公司近日&#xff0c;国内企业管理软件服务商高亚科技与深圳市奕源金属制品有限公司&#xff08;以下简称“奕源金属”&#xff09;正式签约&#xff0c;双方将基于高亚科技自主研发的8Manage SRM采购管理系统&#xff0c;共同推动奕源金属采购管理的数字…

数据结构之map

map的基本介绍我们常常把map称之为映射&#xff0c;就是将一个元素&#xff08;通常称之为key键&#xff09;与一个相对应的值&#xff08;通常称之为value&#xff09;关联起来&#xff0c;比如说一个学生的名字&#xff08;key&#xff09;有与之对应的成绩&#xff08;value…

vue3 canvas 选择器 Canvas 增加页面性能

文章目录Vue3 选择器 Canvas 增加页面性能基于Vue3 Composition API和Canvas实现的交互式选择器&#xff0c;支持PC端和移动端的拖动选择、多选取消选择功能vue3组件封装html代码Vue3 选择器 Canvas 增加页面性能 基于Vue3 Composition API和Canvas实现的交互式选择器&#xf…

Python 实战:打造多文件批量重命名工具

引言在实际运维、测试、数据分析、开发流程中&#xff0c;我们经常会处理成百上千条命令操作&#xff0c;例如&#xff1a;各种脚本任务&#xff08;启动、备份、重启、日志查看&#xff09;数据处理流程&#xff08;爬取 → 清洗 → 统计 → 可视化&#xff09;配置自动化&…

设计模式笔记_结构型_代理模式

1. 代理模式介绍代理模式是一种结构型设计模式&#xff0c;它允许你提供一个代理对象来控制对另一个对象的访问。代理对象通常在客户端和目标对象之间起到中介作用&#xff0c;能够在不改变目标对象的前提下增加额外的功能操作&#xff0c;比如延迟初始化、访问控制、日志记录等…

C语言<数据结构-单链表>(收尾)

上篇博客我将基础的尾插、尾删、头插、头删逐一讲解了&#xff0c;这篇博客将对上篇博客进行收尾&#xff0c;讲一下指定位置操作增删以及查找这几个函数&#xff0c;其实大同小异&#xff1a;一.查找函数&#xff1a;查找函数其实就是一个简单的循环遍历&#xff0c;所以不加以…