Spring 中用到了哪些设计模式?分别用在哪里?
简化版
Spring 是设计模式的「活教材」,几乎处处是模式:单例模式(Bean 默认单例)、工厂模式(BeanFactory/FactoryBean 生产 Bean)、代理模式(AOP 用 JDK/CGLIB 动态代理)、模板方法模式(JdbcTemplate、RestTemplate 封装固定流程、留钩子给你填)、观察者模式(事件机制 ApplicationEvent/Listener)、适配器模式(SpringMVC 的 HandlerAdapter 适配不同类型的 Controller)、装饰器模式(BeanWrapper、各种包装类)、策略模式(资源加载 Resource、实例化策略)。面试常考的核心五个是:单例、工厂、代理、模板方法、观察者。
详细版
Spring 中的主要设计模式:
| 设计模式 | 在 Spring 中的应用 |
|---|---|
| 单例模式 | Bean 默认单例,容器用单例注册表(singletonObjects)缓存 |
| 工厂模式 | BeanFactory(IoC 容器本身就是工厂)、FactoryBean(自定义 Bean 创建逻辑) |
| 代理模式 | AOP——JDK 动态代理(有接口)/ CGLIB(无接口)生成代理增强方法 |
| 模板方法模式 | JdbcTemplate、RestTemplate、TransactionTemplate——固定流程 + 回调钩子 |
| 观察者模式 | 事件机制——ApplicationEventPublisher 发布、ApplicationListener 监听 |
| 适配器模式 | SpringMVC 的 HandlerAdapter 适配不同 Controller;AOP 的 AdvisorAdapter |
| 装饰器模式 | BeanWrapper 包装 Bean;HttpServletRequestWrapper |
| 策略模式 | Resource 资源加载、InstantiationStrategy 实例化策略 |
| 责任链模式 | SpringMVC 的拦截器链、AOP 的拦截器链(MethodInterceptor 链) |
// 模板方法:JdbcTemplate 把「获取连接→执行→处理异常→释放连接」固定住,
// 只留「怎么处理结果」的钩子给你(RowMapper)
List<User> users = jdbcTemplate.query(
"SELECT * FROM user",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")) // 你只写这段回调
);
// 连接的获取、释放、异常处理全被模板封装了,你不用管
⚠️ 面试答「Spring 用了哪些设计模式」时,别只报名字——要说清「用在哪、解决什么问题」。比如「模板方法用在 JdbcTemplate,把 JDBC 的样板代码(开连接、关连接、异常处理)固定住,只留结果处理的钩子给开发者」,这样才显得真懂,而不是背书。
完整版教学
一、为什么 Spring 是设计模式的活教材
Spring 作为一个成熟框架,要解决「灵活扩展、解耦、复用」等问题,而设计模式正是前人总结的「解决这类问题的套路」。所以 Spring 大量运用设计模式,学 Spring 就是学「设计模式怎么落地」:
框架要解决的问题 → 对应的设计模式
对象创建要灵活可配 → 工厂模式(BeanFactory)
全局共享同一个对象 → 单例模式(Bean 单例)
不改原代码增强功能 → 代理模式(AOP)
封装固定流程、开放变化点 → 模板方法模式(JdbcTemplate)
状态变化要通知多方 → 观察者模式(事件机制)
理解「Spring 用设计模式是为了解决具体问题」,答题时就能把「模式 + 场景 + 解决的问题」串起来,而不是干巴巴报名字。下面挑最核心、最常考的几个深入讲。
二、单例模式:Bean 的默认作用域
Spring 的 Bean 默认是单例——整个容器里一个类型只有一个实例,所有依赖它的地方共享。但注意,它和经典的单例模式(构造器私有 + 静态方法)实现方式不同:
经典单例:类自己控制(private 构造器 + getInstance 静态方法)
Spring 单例:容器控制——容器用一个 Map(singletonObjects,叫"单例注册表")缓存 Bean
第一次要 Bean 时创建并放进 Map,之后都从 Map 返回同一个
区别关键:Spring 单例是「容器级单例」不是「JVM 级单例」——同一个容器里单例,但如果有多个容器,每个容器各有一份。而且 Spring 单例默认不是线程安全的(单例 Bean 被多线程共享,如果有可变状态就要自己保证线程安全)。这是「单例 Bean 线程安全吗」的答案——单例只保证「实例唯一」,不保证「线程安全」。
三、工厂模式:BeanFactory 与 FactoryBean
Spring 的 IoC 容器本质就是一个大工厂——你不 new 对象,而是向容器「要」对象,容器负责生产。这里有两个容易混的概念:
BeanFactory:IoC 容器的顶层接口,整个容器就是一个"生产 Bean 的工厂"
你 getBean("xxx") → 工厂给你造好/返回一个 Bean
FactoryBean:一个特殊的 Bean,它本身是"生产某种 Bean 的工厂"
实现 FactoryBean 接口,getObject() 定义"怎么造这个 Bean"
用于创建过程复杂、不方便用普通 @Bean 描述的对象(如 MyBatis 的 SqlSessionFactoryBean)
一个经典追问:BeanFactory 和 FactoryBean 的区别? BeanFactory 是「容器」(管所有 Bean),FactoryBean 是「容器里的一个特殊 Bean」(专门生产某个复杂对象)。取 FactoryBean 生产的对象用 getBean("name"),取 FactoryBean 本身用 getBean("&name")(加 & 前缀)。工厂模式让「对象创建」和「对象使用」解耦——使用方不关心对象怎么造出来的。
四、代理模式:AOP 的根基
代理模式是「不改原对象、通过一个代理来增强它」。Spring AOP 就是代理模式的应用——给目标 Bean 生成代理,在方法调用前后织入增强逻辑(事务、日志、权限):
你调用 userService.save()
实际调的是 代理对象.save()
→ 代理:先做增强(开事务/记日志)
→ 调用真正的 userService.save()
→ 代理:后做增强(提交事务)
两种代理实现:
JDK 动态代理:目标类实现了接口 → 基于接口生成代理
CGLIB:目标类没有接口 → 生成目标类的子类作为代理
代理模式的价值是无侵入增强——userService 的代码一个字不用改,事务、日志这些「横切关注点」通过代理织入。这就是为什么「注入进来的 Bean 常常是代理对象」。Spring 的事务、@Async、@Cacheable、权限控制,全都建立在这个代理机制上。
五、模板方法模式:消灭样板代码
模板方法模式是「把固定的流程骨架封装起来,把变化的部分留成钩子(回调)让你填」。Spring 的各种 XxxTemplate 都是它的应用,最典型是 JdbcTemplate:
JDBC 操作的固定流程(样板代码,每次都一样):
1. 获取数据库连接 ┐
2. 创建 Statement │ 这些每次都要写,繁琐易错
3. 执行 SQL │ (尤其忘了关连接会泄漏)
4. 【处理结果】← 变化点 │ 只有这步因业务而异
5. 处理异常 │
6. 释放连接 ┘
JdbcTemplate 把 1、2、3、5、6 固定封装(模板),
只把第 4 步「怎么处理结果」留成钩子(RowMapper 回调)让你填
→ 你只写业务相关的结果映射,样板代码全省了
价值:消灭重复的样板代码,同时把「容易出错的资源管理」(如关连接)交给模板保证。RestTemplate(HTTP 调用)、TransactionTemplate(编程式事务)、RedisTemplate 都是同一个套路——固定流程 + 开放钩子。理解模板方法,就理解了为什么用 JdbcTemplate 不用手写一堆 try-finally 关连接。
六、观察者、适配器、责任链等其余模式
剩下几个也很常考,一并理清:
| 模式 | Spring 应用 | 解决的问题 |
|---|---|---|
| 观察者 | 事件机制(发布-监听) | 状态变化通知多方,解耦发布与处理 |
| 适配器 | SpringMVC 的 HandlerAdapter | 适配不同类型的 Controller(注解式、实现接口式),用统一方式调用 |
| 责任链 | MVC 拦截器链、AOP 拦截器链 | 多个处理器依次处理请求,可中断 |
| 装饰器 | BeanWrapper、各种 Wrapper | 动态增强对象功能 |
| 策略 | Resource 资源加载、实例化策略 | 运行时选择不同算法/实现 |
以适配器模式为例:SpringMVC 支持多种 Controller 写法(@Controller 注解式、实现 Controller 接口式、HttpRequestHandler 式),但 DispatcherServlet 想用统一方式调用它们,于是用 HandlerAdapter 适配——每种 Controller 有对应的 Adapter,把不同的 Controller「适配」成统一接口。这样 DispatcherServlet 不用为每种 Controller 写 if-else,符合开闭原则。责任链则体现在拦截器:请求依次经过多个拦截器(preHandle),任一拦截器可中断链条——这也是过滤器、AOP 拦截器链的模式。
记忆钩子:「Spring 五大核心模式:单例(Bean 默认,容器级非线程安全)、工厂(BeanFactory 容器/FactoryBean 特殊 Bean)、代理(AOP 用 JDK/CGLIB)、模板方法(JdbcTemplate 固定流程+钩子)、观察者(事件机制);答题要说清’用在哪、解决什么’」。
七、常见误区与追问
- 误区:答这题只要报模式名字。 要说清「用在哪、解决什么问题」,如「模板方法用在 JdbcTemplate,封装 JDBC 样板代码只留结果处理钩子」,否则显得只是背书。
- 误区:Spring 单例就是经典单例模式。 实现不同——经典单例靠类自己(私有构造+静态方法),Spring 单例靠容器用 Map 缓存;且是容器级单例、默认非线程安全。
- 误区:BeanFactory 和 FactoryBean 是一回事。 BeanFactory 是 IoC 容器(管所有 Bean 的工厂);FactoryBean 是容器里一个特殊 Bean(专门生产某个复杂对象),取它生产的对象用 getBean(“name”)、取它本身用 getBean(“&name”)。
- 误区:JdbcTemplate 只是个工具类。 它是模板方法模式的典型——把 JDBC 固定流程(连接、执行、释放、异常)封装成模板,只留 RowMapper 钩子给你,消灭样板代码。
- 追问:AOP 用的是什么设计模式? 代理模式——给目标 Bean 生成代理(JDK 动态代理或 CGLIB),在方法前后织入增强,实现无侵入增强。
- 追问:SpringMVC 的 HandlerAdapter 是什么模式? 适配器模式——适配不同类型的 Controller(注解式/接口式),让 DispatcherServlet 用统一方式调用,避免为每种 Controller 写分支判断。
- 追问:Spring 事件机制是什么模式? 观察者模式——发布器发布事件、监听器订阅处理,解耦发布方和处理方,发布方无需知道有哪些监听器。
八、加强记忆
Spring 是设计模式的活教材,因为它用模式来解决「灵活创建、解耦、无侵入增强、消灭样板」等框架级问题。五个核心必背:① 单例模式——Bean 默认单例,容器用单例注册表(Map)缓存,是「容器级单例」且默认非线程安全;② 工厂模式——BeanFactory 是 IoC 容器(生产所有 Bean 的大工厂),FactoryBean 是容器里专门生产某复杂对象的特殊 Bean(getBean("&name") 取它本身);③ 代理模式——AOP 用 JDK 动态代理(有接口)/ CGLIB(无接口)生成代理,无侵入织入事务/日志/权限,是「注入的是代理」的根源;④ 模板方法模式——JdbcTemplate/RestTemplate 把固定流程(连接、执行、释放、异常)封装成模板,只留 RowMapper 等钩子,消灭样板代码;⑤ 观察者模式——事件机制发布-监听解耦。其余:适配器(HandlerAdapter 适配不同 Controller)、责任链(拦截器链)、装饰器(Wrapper)、策略(资源加载)。答题关键是**说清「用在哪、解决什么问题」**而非报名字。一句话「单例(Bean)、工厂(容器)、代理(AOP)、模板方法(JdbcTemplate)、观察者(事件)是五大核心,答题带场景」。