前言
在前五篇文章中,我们拆解了IoC、AOP、SpringMVC、自动配置和事务管理。每个模块内部都用到了多种设计模式。现在换个视角,从设计模式的角度重新审视Spring,你会发现这些看似独立的模块,底层共享着同样的设计智慧。
面试中,这个问题是检验你是否"理解Spring"而非只是"会用Spring"的试金石:
"Spring中用到了哪些设计模式?各在什么地方用的?"
"BeanFactory和FactoryBean有什么区别?"
"Spring的AOP是用的哪种代理模式?"
本文串联前五篇的知识,拆解Spring中最核心的六种设计模式,让你从设计的视角重新理解Spring。
本文核心问题:
- 单例模式:Spring的Bean默认是单例的,底层怎么保证只有一个实例?
- 工厂模式:BeanFactory和FactoryBean有什么区别?为什么有两个"工厂"?
- 代理模式:AOP是如何运用动态代理的?JDK代理和CGLIB各在什么场景下使用?
- 模板方法模式:JdbcTemplate、RestTemplate的设计思想是什么?
- 观察者模式:ApplicationEvent和ApplicationListener如何实现事件驱动?
- 策略模式:Spring中是如何通过不同环境加载不同配置的?
- 这些设计模式是如何协同工作,共同构成Spring框架的?
读完本文,你将从设计模式的视角,对前五篇文章的知识做一次完整的串联和升华。
一、单例模式------Spring的Bean默认是单例的
疑问:Spring的Bean默认是单例的,这是怎么实现的?和手写单例模式有什么区别?
回答:Spring的单例是通过"容器内唯一"来保证的------同一个ApplicationContext中,相同id的Bean只创建一次。它不依赖static、不依赖私有构造器,而是通过容器的统一管理来约束实例的唯一性。
1.1 和传统单例的区别
java
// 传统手写单例:靠语言特性强制唯一
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // 私有构造器禁止外部new
public static Singleton getInstance() { return INSTANCE; }
}
// Spring单例:靠容器管理保证唯一
@Component // 无私有构造器限制
public class OrderService { // 可以被new,可以创建多个实例(通过多个容器)
// ...
}
Spring的单例不依赖语言特性,而是依赖容器的生命周期管理。 容器启动时创建所有单例Bean的实例并存入一级缓存,后续获取时从缓存中直接返回。传统单例通过私有构造器在编码层面禁止外部创建新实例,而Spring的Bean可以被new、可以被反射调用------它的唯一性由容器维护,而非语言约束。
1.2 在哪用到?
Spring管理的所有默认Bean都是单例的------Controller、Service、Repository、以及框架内部的DispatcherServlet、HandlerMapping等都是单例。这就是为什么这些组件中不能持有可变状态------单例意味着所有请求共享同一个实例,状态字段会在并发访问下产生数据错乱。
二、工厂模式------BeanFactory和FactoryBean有什么区别?
疑问:Spring中有BeanFactory和FactoryBean,它们有什么不同?为什么需要两个"工厂"?
回答:BeanFactory是Spring的IoC容器本身------它管理所有Bean的创建。FactoryBean是Bean的"定制工厂"------当一个Bean的创建逻辑比较复杂,无法用简单的new或反射完成时,FactoryBean封装了这个复杂的创建过程。
2.1 BeanFactory------容器即工厂
java
// BeanFactory是Spring容器的顶层接口
// 它的职责是:根据beanName返回创建好的Bean实例
public interface BeanFactory {
Object getBean(String name);
<T> T getBean(Class<T> requiredType);
}
BeanFactory就是Spring IoC容器本身 ------它是所有Bean的统一管理入口。你调用getBean(),它从缓存中取出已经创建好的实例返回。
2.2 FactoryBean------定制Bean的创建逻辑
java
// FactoryBean用于封装复杂Bean的创建过程
public interface FactoryBean<T> {
T getObject() throws Exception; // 创建Bean实例
Class<?> getObjectType(); // Bean的类型
boolean isSingleton(); // 是否是单例
}
// 例如:MyBatis的SqlSessionFactoryBean
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
@Override
public SqlSessionFactory getObject() throws Exception {
// 复杂的创建逻辑:解析XML、构建Configuration、创建SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
return builder.build(this.configuration);
}
}
占位汇总 :FactoryBean封装的是"怎么创建"的逻辑,BeanFactory管理的是"什么时候给、给哪个实例"的调度。注入的是SqlSessionFactory而不是SqlSessionFactoryBean本身。
2.3 两者本质区别
BeanFactory是容器层------管理所有Bean的生命周期,调用者是Spring框架本身。FactoryBean是Bean层------封装单个复杂Bean的创建逻辑,调用者是需要定制化创建Bean的开发者。BeanFactory用工厂方法模式统一了Bean的获取入口,FactoryBean用工厂方法模式封装了单个Bean的创建复杂度。两者的分工清晰------BeanFactory管全局,FactoryBean管局部细节。
三、代理模式------AOP的核心基石
疑问:AOP是怎么通过代理模式实现的?JDK动态代理和CGLIB各在什么场景下使用?
回答:Spring AOP通过生成代理对象,在代理中插入切面逻辑,再把调用转发给原始对象。JDK动态代理要求目标对象实现接口,CGLIB通过继承目标类生成代理子类------两者都是代理模式在运行时的不同实现。
3.1 和静态代理的区别
java
// 静态代理:手写代理类(每代理一个类就要写一个代理)
public class OrderServiceStaticProxy implements OrderService {
private OrderService target;
public void createOrder(Order order) {
log.info("开始");
target.createOrder(order); // 代理中插入日志
log.info("结束");
}
}
// 动态代理:运行时动态生成代理(一套逻辑代理所有类)
// JDK动态代理:
OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
log.info("开始");
Object result = method.invoke(target, args);
log.info("结束");
return result;
}
);
3.2 Spring AOP = 动态代理 + IoC
Spring AOP将代理的生成和Bean的生命周期融合在一起------在Bean初始化的最后阶段,通过BeanPostProcessor判断是否需要为Bean生成代理。如果需要,返回代理对象替代原始Bean。此后容器中持有的就是代理对象,所有对该Bean的调用都经过代理------切面逻辑在其中生效。
这就是为什么自调用事务失效:this调用的是原始对象,而不是容器返回的代理对象------代理的拦截链从未被触发。
四、模板方法模式------JdbcTemplate的设计思想
疑问:JdbcTemplate为什么能省掉大量重复代码?它用的是什么设计模式?
回答:JdbcTemplate用的是模板方法模式------把"获取连接→执行SQL→处理结果→关闭资源"这个固定流程定义在父类中,只留出SQL执行和结果处理两个可变部分给子类或回调实现。
4.1 没有模板方法时
java
// 手写JDBC:每次都要重复获取连接、关闭资源
public List<User> getUsers() {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection(); // ← 重复
stmt = conn.prepareStatement("SELECT * FROM user"); // ← 可变
rs = stmt.executeQuery(); // ← 重复
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(new User(rs.getLong("id"), rs.getString("name"))); // ← 可变
}
return users;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (rs != null) try { rs.close(); } catch (SQLException e) {} // ← 重复
if (stmt != null) try { stmt.close(); } catch (SQLException e) {} // ← 重复
if (conn != null) try { conn.close(); } catch (SQLException e) {} // ← 重复
}
}
4.2 JdbcTemplate的模板方法
java
// JdbcTemplate帮你管理所有重复的部分
List<User> users = jdbcTemplate.query(
"SELECT * FROM user", // SQL(可变部分)
(rs, rowNum) -> new User( // 结果映射(可变部分)
rs.getLong("id"),
rs.getString("name")
)
);
固定部分被封装 :获取连接、创建PreparedStatement、执行查询、遍历ResultSet、关闭资源------全由JdbcTemplate内部管理。可变部分由开发者传入:SQL语句和结果映射逻辑。开发者只需要关心"查什么"和"结果怎么映射",不需要写任何连接和资源管理代码。
4.3 还有哪些用了模板方法?
| 组件 | 固定流程 | 可变部分(回调) |
|---|---|---|
| JdbcTemplate | 获取连接→执行→处理结果→关闭 | SQL语句、结果集映射 |
| RestTemplate | 建立HTTP连接→发送请求→接收响应→关闭 | URL、请求方法、响应体映射 |
| TransactionTemplate | 开启事务→执行业务→提交/回滚→清理 | 业务逻辑 |
| RedisTemplate | 获取连接→序列化→执行命令→反序列化→关闭 | Key、Value、Redis命令 |
五、观察者模式------ApplicationEvent和ApplicationListener
疑问:Spring的事件机制是干什么的?用的是哪种设计模式?
回答:Spring的事件机制用的是观察者模式------Bean可以发布事件,监听该事件的Listener在事件发布时自动被调用。这是Bean之间解耦通信的重要手段。
5.1 典型使用场景
java
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
}
// 2. 发布事件(在创建订单的Service中)
@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder(Order order) {
orderMapper.insert(order);
publisher.publishEvent(new OrderCreatedEvent(this, order)); // 发布事件
// 订单创建Service不关心谁来监听,它只知道"有订单创建了"
}
}
// 3. 监听事件(在积分Service中,完全解耦)
@Component
public class PointService {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 增加用户积分------和订单创建逻辑完全解耦!
pointMapper.addPoint(event.getOrder().getUserId(), 10);
}
}
// 4. 再加一个监听(短信通知),完全不修改订单创建逻辑
@Component
public class SmsService {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
smsUtil.send(event.getOrder().getUserId(), "订单已创建");
}
}
事件驱动的好处:订单创建逻辑不需要知道积分逻辑的存在,也不需要知道短信逻辑的存在。增加新的监听者(如记录操作日志)时,对订单创建代码零修改------符合开闭原则。
5.2 Spring用在哪?
- 容器生命周期事件 :
ContextRefreshedEvent(容器刷新完成)、ContextClosedEvent(容器关闭)。Spring内部的扩展点大多通过事件机制触发 - Spring Security:认证成功/失败事件、授权事件
- 业务层面的事件解耦:订单创建、用户注册等,将主流程和后续通知、积分、日志等副作用分离
六、策略模式------不同环境加载不同配置
疑问:Spring怎么做到开发环境一套配置,生产环境另一套配置?
回答:用的是策略模式------定义一套配置加载的策略接口,不同环境使用不同的策略实现。
6.1 Spring中的策略模式
yaml
# application-dev.yml(开发环境)
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
# application-prod.yml(生产环境)
spring:
datasource:
url: jdbc:mysql://prod-cluster:3306/prod_db
java
// 激活不同的配置策略
// 通过 spring.profiles.active=dev 或 prod 切换
Spring的Profile机制本质就是策略模式:定义配置项的统一Profile策略接口,不同Profile(dev/prod/test)提供不同的属性值作为该策略的实现。运行时根据激活的Profile选择对应的策略实现进行组合。
6.2 还有哪些策略模式应用?
| 策略 | 场景 | Spring中的体现 |
|---|---|---|
| 配置文件 | 不同环境 | Profile + application-{profile}.yml |
| 代理方式 | 目标类是否实现接口 | JDK动态代理 vs CGLIB |
| 事务管理 | 不同数据源 | JpaTransactionManager vs DataSourceTransactionManager |
| 视图解析 | 不同模板引擎 | ThymeleafViewResolver vs InternalResourceViewResolver |
七、六种模式如何协同工作?
Spring框架全景图(设计模式视角):
单例模式
└── 容器中所有Bean默认单例,由IoC容器统一管理生命周期
工厂模式
├── BeanFactory:全局Bean注册表(容器层)
└── FactoryBean:封装复杂Bean的创建过程(Bean层)
代理模式
├── AOP:为Bean生成代理,插入切面逻辑
├── @Transactional:代理中包裹事务管理
└── @Cacheable:代理中包裹缓存管理
模板方法模式
├── JdbcTemplate:管理JDBC的固定流程
├── RestTemplate:管理HTTP的固定流程
└── TransactionTemplate:管理事务的固定流程
观察者模式
├── ApplicationEvent:容器生命周期事件
└── @EventListener:解耦Bean之间的通信
策略模式
├── Profile:多环境配置切换
└── 代理选择:JDK vs CGLIB
单例模式是容器管理的基础------所有Bean默认单例,统一生命周期。工厂模式提供了Bean的创建能力------BeanFactory管全局,FactoryBean管局部复杂创建。代理模式在工厂模式之上为Bean生成代理------AOP将切面逻辑透明插入。模板方法模式封装了事务、JDBC的固定流程------让开发者只关心可变部分。观察者模式解耦Bean之间的通信------发布事件不依赖监听者的存在。策略模式让不同环境的配置和行为切换透明------对外暴露统一接口,对内封装不同的实现。
八、面试中这样回答
面试官:"Spring中用到了哪些设计模式?"
回答框架:
"最核心的有六种。单例模式------所有Bean默认单例,由容器统一管理。工厂模式------BeanFactory是容器的顶层接口,FactoryBean封装复杂Bean的创建过程。代理模式------AOP通过动态代理实现,JDK代理要求接口,CGLIB通过继承生成子类,也是@Transactional和@Cacheable的底层机制。模板方法模式------JdbcTemplate、RestTemplate把固定流程封装在内部,可变部分留为回调。观察者模式------ApplicationEvent和@EventListener实现Bean之间的解耦通信。策略模式------Profile机制支持多环境配置切换。这些模式不是各自独立的------单例和工厂是容器的基础,代理在工厂之上为Bean生成增强对象,模板方法封装了框架内部的重试和事务流程,观察者和策略让业务和配置更灵活。"
总结
- 单例模式:Spring容器通过一级缓存保证每个Bean定义只创建一个实例,是所有Bean管理的基石
- 工厂模式:BeanFactory是容器层工厂,FactoryBean封装单个复杂Bean的创建。前者管全局,后者管局部细节
- 代理模式:Spring AOP的灵魂------JDK代理要求接口,CGLIB通过继承生成子类。@Transactional和@Cacheable都是代理模式的延伸
- 模板方法模式:JdbcTemplate、RestTemplate、TransactionTemplate将固定流程抽离为框架代码,可变部分留给回调实现
- 观察者模式:ApplicationEvent和@EventListener实现Bean间的解耦通信------发布者完全不依赖监听者的存在
- 策略模式:Profile机制切换不同环境的配置和实现------开发/测试/生产环境对外暴露统一接口,对内封装各自的策略
- 模式协同:这六种模式相辅相成------单例和工厂是容器的根基,代理在工厂之上生成增强对象,模板方法封装框架内的固定流程,观察者和策略让通信和配置更灵活
下一篇预告:Spring原理(七)------Spring扩展点:如何优雅地介入Bean的创建流程。拆解BeanPostProcessor、BeanFactoryPostProcessor、InitializingBean、Aware等Spring内置扩展点的执行时机和使用场景,配合权限系统中自定义注解的实际应用。