当前位置: 首页 > ai >正文

从 Spring 源码到项目实战:设计模式落地经验与最佳实践

在实际项目开发中,Spring 框架的设计模式并非停留在源码层面的 “理论概念”,而是解决复杂业务问题的 “实用工具”。结合多年的项目经验,我想从创建型、结构型、行为型三大类设计模式入手,分享一些真实场景下的应用心得,以及这些模式如何帮助团队提升代码质量和开发效率。

一、创建型模式:解决 “对象创建” 的痛点,让依赖更可控

创建型模式的核心是 “如何灵活、高效地创建对象”,在实际项目中,对象的创建往往伴随着复杂依赖、资源管理等问题,Spring 的创建型模式恰好能化解这些痛点。

1. 工厂模式:从 “硬编码 new 对象” 到 “容器托管”,解耦依赖关系

场景:在一个电商项目中,订单服务(OrderService)需要依赖库存服务(InventoryService)、支付服务(PaymentService),而这些服务又依赖数据库连接、缓存客户端等资源。如果直接在 OrderService 中用new创建依赖,会导致:

  • 依赖关系硬编码,修改一个服务的实现类(比如从 MySQL 切换到 PostgreSQL),需要修改所有依赖它的类;
  • 对象创建逻辑分散,比如库存服务需要初始化缓存连接,这些逻辑散落在各个地方,难以维护。

解决方案:借鉴 Spring 的BeanFactory思想,用工厂模式统一管理对象创建。

  • 定义一个全局的 “服务工厂”(类似DefaultListableBeanFactory),在工厂中注册所有服务的创建逻辑(比如InventoryService的创建需要先初始化 Redis 连接);
  • 订单服务通过工厂的getBean("inventoryService")获取依赖,而非直接new

效果

  • 依赖关系由工厂集中管理,修改服务实现时,只需更新工厂中的创建逻辑,无需改动所有依赖类;
  • 支持 “延迟加载”,服务在第一次被使用时才创建,减少项目启动时的资源消耗。
2. 单例模式:避免 “重复创建”,节省资源开销

场景:在一个数据同步项目中,有一个ElasticSearchClient工具类,负责与 ES 集群建立连接。如果每次调用同步方法都new一个客户端,会导致:

  • 频繁创建 TCP 连接,消耗 ES 集群的连接数,甚至触发限流;
  • 客户端初始化时需要加载配置(比如集群地址、认证信息),重复加载造成性能浪费。

解决方案:借鉴 Spring 的@Singleton(默认作用域),将ElasticSearchClient设计为单例。

  • 在服务工厂中,通过getSingletonBean方法确保客户端只被创建一次,后续调用直接返回缓存的实例;
  • 结合 “双重检查锁”(DCL)处理并发场景,避免多线程下重复创建。

注意:单例模式并非 “万能药”,对于有状态的对象(比如包含用户会话信息的UserContext),绝对不能用单例,否则会导致线程安全问题(多个线程共享同一实例,数据混乱)。Spring 中通过@Scope("prototype")解决这类问题,我们在项目中也可以借鉴 —— 为有状态对象设置 “原型作用域”,每次获取都创建新实例。

3. 建造者模式:拆解 “复杂对象” 的创建,避免 “参数爆炸”

场景:在一个报表系统中,需要创建ReportQueryParam对象,用于传递报表查询参数。这个对象包含:时间范围(开始时间、结束时间)、数据维度(用户、地区、产品)、过滤条件(多个Filter对象)、分页参数(页码、页大小)等 20 + 个参数。如果用构造器或setter创建,会导致:

  • 构造器参数列表过长,可读性差,且容易传错顺序;
  • 部分参数是可选的(比如分页参数),但setter调用分散,难以确认是否遗漏必填参数(比如开始时间)。

解决方案:借鉴 Spring 的BeanDefinitionBuilder,用建造者模式封装ReportQueryParam的创建。

// 建造者类
public class ReportQueryParamBuilder {private ReportQueryParam param = new ReportQueryParam();public ReportQueryParamBuilder timeRange(Date start, Date end) {param.setStartTime(start);param.setEndTime(end);return this;}public ReportQueryParamBuilder addFilter(Filter filter) {param.getFilters().add(filter);return this;}// 其他参数设置方法...public ReportQueryParam build() {// 校验必填参数if (param.getStartTime() == null) {throw new IllegalArgumentException("开始时间不能为空");}return param;}
}// 使用时
ReportQueryParam param = new ReportQueryParamBuilder().timeRange(start, end).addFilter(new Filter("region", "华东")).page(1, 10).build();

效果

  • 创建逻辑链式调用,参数关系清晰,可读性大幅提升;
  • build()方法中统一校验必填参数,避免因参数缺失导致的运行时错误。

二、结构型模式:解决 “对象组合” 的问题,让系统更灵活

结构型模式关注 “如何将对象组合成更大的结构”,在实际项目中,我们常需要集成不同组件、扩展现有功能,这时候结构型模式能派上大用场。

1. 代理模式:无侵入增强,让 “通用逻辑” 与 “业务逻辑” 分离

场景:在一个用户中心项目中,需要为所有接口添加 “日志记录”(记录请求参数、响应结果)和 “权限校验”(检查用户是否有访问权限)。如果直接在每个接口方法中写这些逻辑,会导致:

  • 代码冗余:每个方法都有重复的日志和权限代码;
  • 业务逻辑被污染:核心功能(比如用户注册、登录)与非核心逻辑混杂,难以维护。

解决方案:借鉴 Spring AOP 的 “动态代理” 思想,用代理模式对接口进行增强。

  • 定义一个UserServiceProxy,实现UserService接口,在invoke方法中先执行日志记录和权限校验,再调用真实的UserServiceImpl
  • 通过 Spring 的@Aspect注解(底层是 JDK 动态代理或 CGLIB),甚至可以更简单:直接用Proxy.newProxyInstance创建代理对象,无需手动编写代理类。

效果

  • 通用逻辑(日志、权限)集中在代理类中,业务类只关注核心功能,符合 “单一职责原则”;
  • 后续需要添加新的增强(比如接口耗时统计),只需修改代理类,无需改动所有业务方法。
2. 适配器模式:化解 “接口不兼容”,让异构组件和谐共处

场景:在一个支付系统中,需要集成支付宝、微信支付、银联三种支付方式。这三种支付接口的方法名和参数完全不同:

  • 支付宝:AlipayClient.pay(String orderNo, BigDecimal amount)
  • 微信支付:WechatPayClient.doPayment(String outTradeNo, double money, String openId)
  • 银联:UnionPayClient.processPayment(PaymentRequest request)

如果直接在业务代码中调用这些接口,会导致支付逻辑与具体的支付厂商强耦合,后续接入新支付方式(比如 PayPal)时,需要修改大量业务代码。

解决方案:借鉴 Spring MVC 的HandlerAdapter,用适配器模式统一支付接口。

  • 定义统一的PaymentAdapter接口:void pay(Order order)
  • 为每种支付方式编写适配器:AlipayAdapter实现PaymentAdapter,内部调用AlipayClient.payWechatPayAdapter调用WechatPayClient.doPayment
  • 业务代码只需调用PaymentAdapter.pay(order),无需关心具体是哪种支付方式。

效果

  • 业务代码与具体支付厂商解耦,接入新支付方式时,只需新增一个适配器,无需修改业务逻辑;
  • 统一的接口便于做通用处理(比如支付结果回调的统一封装)。
3. 装饰器模式:“层层包裹” 实现功能叠加,避免继承爆炸

场景:在一个文件上传系统中,需要对上传的文件进行一系列处理:校验文件大小、检查文件类型、添加水印、压缩图片。如果用继承实现(比如WatermarkFileUploader extends FileUploaderCompressFileUploader extends WatermarkFileUploader),会导致:

  • 继承链越来越长,且功能组合固定(比如 “压缩 + 水印” 和 “水印 + 压缩” 需要两个不同的子类);
  • 新增处理步骤(比如病毒扫描)时,需要修改所有相关的子类。

解决方案:借鉴 Spring 对HttpServletRequest的装饰(HttpServletRequestWrapper),用装饰器模式动态扩展功能。

  • 定义FileUploader接口:void upload(File file)
  • 编写基础实现DefaultFileUploader(仅负责文件上传到服务器);
  • 为每个处理步骤编写装饰器:SizeCheckDecorator(校验大小)、WatermarkDecorator(添加水印),每个装饰器持有一个FileUploader实例,在upload方法中先执行自己的逻辑,再调用被装饰者的upload

使用时

// 组合“校验大小+添加水印+压缩”功能
FileUploader uploader = new CompressDecorator(new WatermarkDecorator(new SizeCheckDecorator(new DefaultFileUploader()))
);
uploader.upload(file);

效果

  • 功能可以按需组合,无需修改现有类,符合 “开闭原则”;
  • 每个装饰器只关注一个处理步骤,代码简洁,易于测试。

三、行为型模式:优化 “对象交互”,让流程更清晰、扩展更灵活

行为型模式聚焦于 “对象之间的协作方式”,在实际项目中,复杂的业务流程(比如订单处理、审批流)、事件通知等场景,都需要通过行为型模式梳理交互逻辑。

1. 模板方法模式:固化 “重复流程”,让变化部分 “按需定制”

场景:在一个数据导出系统中,导出 Excel 的流程是固定的:“查询数据→转换为 Excel 格式→写入文件→发送邮件通知”,但不同业务(比如用户数据导出、订单数据导出)的 “查询数据” 和 “转换格式” 步骤完全不同。如果每个业务都重写完整流程,会导致大量重复代码(比如文件写入、邮件通知)。

解决方案:借鉴 SpringAbstractBeanFactory的模板方法,定义流程骨架,让子类实现变化部分。

  • 定义抽象类AbstractExcelExporter,其中export()方法是模板:

    java

    public abstract class AbstractExcelExporter {// 模板方法:固定流程public final void export(ExportParam param) {List<?> data = queryData(param); // 变化部分:子类实现Workbook workbook = convertToExcel(data); // 变化部分:子类实现String filePath = writeToFile(workbook); // 固定部分sendNotification(param.getUserId(), filePath); // 固定部分}// 抽象方法:留给子类实现protected abstract List<?> queryData(ExportParam param);protected abstract Workbook convertToExcel(List<?> data);// 固定方法:通用逻辑private String writeToFile(Workbook workbook) { ... }private void sendNotification(Long userId, String filePath) { ... }
    }
    
  • 子类UserExcelExporter只需实现queryData(查询用户数据)和convertToExcel(用户数据转 Excel)即可。

效果

  • 重复流程(文件写入、邮件通知)被固化在父类,避免代码冗余;
  • 子类只关注自己的业务差异,代码量减少,且流程统一,降低出错概率。
2. 观察者模式:解耦 “事件发布” 与 “事件处理”,提升系统响应能力

场景:在一个电商订单系统中,当订单状态变为 “已支付” 时,需要触发一系列操作:扣减库存、生成物流单、发送支付成功短信、添加用户积分。如果在订单支付的方法中直接调用这些操作,会导致:

  • 订单服务与库存、物流等服务强耦合,任何一个服务的修改(比如积分规则变更)都可能影响订单服务;
  • 新增操作(比如推送支付消息到 APP)时,需要修改订单支付的核心代码,风险高。

解决方案:借鉴 Spring 的ApplicationEventApplicationListener,用观察者模式实现事件驱动。

  • 定义 “订单支付事件”OrderPaidEvent,包含订单 ID、支付金额等信息;
  • 订单服务在支付成功后,发布事件:eventPublisher.publishEvent(new OrderPaidEvent(order))
  • 库存服务、物流服务等分别实现EventListener接口,监听OrderPaidEvent,并处理自己的业务(扣库存、生成物流单)。

效果

  • 订单服务只负责发布事件,不关心谁处理事件,耦合度大幅降低;
  • 新增事件处理者(比如 “推荐系统根据支付订单推送商品”)时,只需新增一个监听器,无需修改订单服务代码,扩展成本极低。
3. 责任链模式:拆分 “复杂校验 / 处理”,让流程更灵活、可配置

场景:在一个用户注册系统中,注册前需要进行多步校验:“手机号格式校验→验证码校验→密码强度校验→用户是否已存在校验”。如果将这些校验写在一个方法中,会导致:

  • 校验逻辑臃肿,一个方法几百行代码,难以维护;
  • 校验顺序固定(比如必须先校验手机号再校验验证码),后续需要调整顺序(比如先校验用户是否存在)时,需要大幅修改代码。

解决方案:借鉴 Spring 拦截器链(HandlerInterceptor),用责任链模式拆分校验步骤。

  • 定义校验处理器接口ValidationHandlerboolean validate(User user, ValidationChain chain)
  • 每个校验步骤对应一个处理器:PhoneValidatorCaptchaValidatorPasswordValidator
  • 责任链ValidationChain持有处理器列表,依次调用每个处理器的validate方法,处理器可以决定是否继续调用下一个(比如手机号校验失败,直接返回,不再执行后续校验)。

使用时

// 构建责任链
ValidationChain chain = new ValidationChain().addHandler(new PhoneValidator()).addHandler(new CaptchaValidator()).addHandler(new PasswordValidator());// 执行校验
if (chain.validate(user)) {// 注册用户
}

效果

  • 每个校验逻辑独立,代码清晰,易于测试和修改;
  • 校验顺序通过链的组装灵活调整,甚至可以根据业务场景动态增减处理器(比如内部用户注册跳过验证码校验)。

四、经验总结:设计模式不是 “银弹”,但能让代码 “有章可循”

在实际项目中应用 Spring 的设计模式,有几个关键点需要注意:

  1. “为解决问题而用”,而非 “为用而用”:设计模式是工具,不是目的。比如单例模式适合无状态对象,但强行用在有状态对象上会导致线程安全问题;责任链模式适合步骤拆分,但链条过长会增加调试难度(不知道哪一步出了问题)。

  2. “借鉴 Spring,但不盲从 Spring”:Spring 的设计模式是为框架通用性服务的(比如BeanFactory需要支持各种 Bean 的创建),但项目中可以简化。比如不需要像 Spring 那样实现完整的BeanDefinition,一个简单的工厂类可能就足够解决依赖问题。

  3. “结合业务复杂度选择模式”:小项目、简单场景(比如一个单表 CRUD 接口),没必要用代理、观察者等模式,否则会增加代码复杂度;但中大型项目、核心流程(比如支付、订单处理),适当引入设计模式能显著提升可维护性。

  4. “通过测试验证模式的合理性”:好的设计模式应该让代码更易测试。比如用代理模式分离的通用逻辑,可以单独测试;用观察者模式的事件处理,能方便地模拟事件进行单元测试。

总之,Spring 框架的设计模式不是 “书本上的概念”,而是无数开发者在解决复杂问题时沉淀的经验。在实际项目中,理解这些模式的 “设计初衷”(比如工厂模式解耦依赖、代理模式实现无侵入增强),并结合自身业务场景灵活运用,才能真正发挥其价值 —— 让代码从 “能跑” 变得 “好维护、可扩展”。


如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!

http://www.xdnf.cn/news/15184.html

相关文章:

  • nginx反向代理实现跨域请求
  • 基于springboot+Vue的二手物品交易的设计与实现
  • ABP VNext + OpenTelemetry + Jaeger:分布式追踪与调用链可视化
  • C语言32个关键字
  • WebGL简易教程——结语
  • 可穿戴智能硬件在国家安全领域的应用
  • Openpyxl:Python操作Excel的利器
  • 10. 垃圾回收的算法
  • JVM 中“对象存活判定方法”全面解析
  • java单例设计模式
  • 小白入门:通过手搓神经网络理解深度学习
  • 6. JVM直接内存
  • 机器学习(ML)、深度学习(DL)、强化学习(RL)关系和区别
  • Linux之如何用contOs 7 发送邮件
  • LeetCode 3169.无需开会的工作日:排序+一次遍历——不需要正难则反,因为正着根本不难
  • 【Modern C++ Part9】Prefer-alias-declarations-to-typedefs
  • 【PTA数据结构 | C语言版】出栈序列的合法性
  • 使用FastAdmin框架开发二
  • Python 实战:构建 Git 自动化助手
  • 昇腾FAQ-A06-行业应用MindX相关
  • hiredis: 一个轻量级、高性能的 C 语言 Redis 客户端库
  • 【世纪龙科技】新能源汽车结构原理体感教学软件-比亚迪E5
  • 代码训练LeetCode(45)旋转图像
  • 知识蒸馏中的教师模型置信度校准:提升知识传递质量的关键路径
  • git版本发布
  • 企业选择大带宽服务器租用的原因有哪些?
  • 电商广告市场惊现“合规黑洞”,企业如何避免亿元罚单
  • Python后端项目之:我为什么使用pdm+uv
  • Java文件传输要点
  • QT跨平台应用程序开发框架(6)—— 常用显示类控件