设计模式在Spring框架中的应用:IoC、AOP、MVC、Template
说实话,Spring框架能火这么多年,不是没有道理的。我最早接触Spring时,第一反应是「这不就是个对象容器吗?」后来踩的坑多了,才慢慢品出味道——Spring本质上是一套设计模式的集大成者。今天我就带你拆解一下,Spring到底用了哪些设计模式,以及我们在实际项目中该怎么借鉴。
IoC容器:单例模式与工厂模式的完美融合
IoC(控制反转)是Spring的基石。说白了,就是把对象的创建权交给容器,我们只管用。这里面藏着两个经典模式。
单例模式:Spring Bean的默认Scope
Spring容器管理的Bean,默认就是单例的。这意味着在整个容器生命周期内,同一个Bean只会创建一个实例。
核心要点:Spring的单例实现与传统的「饿汉式」「懒汉式」不同,它用的是注册表模式——Bean定义注册到容器,第一次请求时创建,然后缓存起来。
// Spring内部简化版单例实现
public class DefaultSingletonBeanRegistry {
// 一级缓存:成品Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露的Bean(解决循环依赖)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:Bean工厂(延迟创建代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
public Object getSingleton(String beanName) {
// 先从一级缓存拿
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 再从二级缓存拿
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 最后从三级缓存拿,并创建Bean
ObjectFactory<?> factory = this.singletonFactories.get(beanName);
if (factory != null) {
singletonObject = factory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
}
我的经验:我曾经在一个高并发项目中,把Service层Bean设成了prototype作用域,结果每次请求都创建新对象,GC压力暴增。后来改成单例,性能提升了30%。记住:无状态Bean用单例,有状态Bean才考虑原型。
工厂模式:BeanFactory与ApplicationContext
Spring的工厂模式体现在两个层面:
- BeanFactory:最基础的工厂,延迟初始化,按需创建Bean
- ApplicationContext:增强版工厂,预加载所有单例Bean,支持国际化、事件发布等
你想想看,这其实就是简单工厂 + 抽象工厂的变体。BeanDefinition就是产品描述,BeanFactory就是生产线,而ApplicationContext就是智能工厂。
// 工厂方法模式在Spring中的体现
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
}
// 抽象工厂:根据不同的Bean定义创建不同类型的Bean
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport {
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args);
}
AOP:代理模式的教科书级应用
AOP(面向切面编程)是Spring的另一大杀器。它的底层实现,说白了就是动态代理。
JDK动态代理 vs CGLIB代理
| 特性 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 目标对象要求 | 必须实现接口 | 无需接口 |
| 代理生成方式 | 运行时生成接口实现类 | 运行时生成子类 |
| 性能 | 创建快,调用慢 | 创建慢,调用快 |
| Spring默认策略 | 有接口时优先使用 | 无接口或配置强制时使用 |
避坑指南:我曾经在项目中遇到一个诡异问题——事务注解不生效。排查了半天,发现是Service类没有实现接口,而Spring默认用了CGLIB代理。但CGLIB代理有个限制:final方法无法被代理。那个方法恰好被标记了final,导致事务切面没织入进去。从那以后,我写Service类都习惯性加上接口。
// Spring AOP代理的核心逻辑(简化版)
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass.isInterface()) {
// 目标类是接口,用JDK动态代理
return new JdkDynamicAopProxy(config);
}
// 否则用CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
}
MVC:观察者模式与中介者模式的巧妙结合
Spring MVC的设计,其实藏着两个模式:观察者模式和中介者模式。
观察者模式:事件驱动机制
Spring的事件机制,就是典型的观察者模式。ApplicationEvent是事件,ApplicationListener是观察者,ApplicationEventPublisher是主题。
// 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private Long orderId;
public OrderCreatedEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
}
// 事件监听器
@Component
public class OrderEventListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
// 处理订单创建后的逻辑:发短信、更新库存等
System.out.println("订单创建成功,订单ID:" + event.getOrderId());
}
}
// 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// 保存订单...
publisher.publishEvent(new OrderCreatedEvent(this, order.getId()));
}
}
我的建议:在微服务架构中,我习惯用Spring事件来做本地事务内的异步处理。比如订单创建后,需要发短信、更新ES索引、记录日志——这些操作如果都写在主流程里,事务会很长。用事件解耦后,主流程只做核心操作,监听器各自处理自己的事,清爽多了。
中介者模式:DispatcherServlet的核心角色
DispatcherServlet就是Spring MVC的中介者。它不直接处理业务,而是协调各个组件:HandlerMapping找处理器,HandlerAdapter执行处理器,ViewResolver解析视图。
// DispatcherServlet的核心流程(简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
// 1. 通过HandlerMapping找到对应的处理器
HandlerExecutionChain mappedHandler = getHandler(request);
// 2. 通过HandlerAdapter找到适配的执行器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 执行处理器,返回ModelAndView
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 4. 通过ViewResolver解析视图
processDispatchResult(request, response, mappedHandler, mv, exception);
}
你想想看,如果没有DispatcherServlet这个中介者,每个Controller都要自己处理请求分发、参数解析、视图渲染——那代码得多乱?中介者模式的核心价值就是降低组件间的耦合度。
Template:模板方法模式的优雅实践
Spring中模板方法模式用得最典型的就是JdbcTemplate、RestTemplate、TransactionTemplate这些。它们把固定的流程封装好,把变化的部分留给子类或回调。
JdbcTemplate的设计思路
数据库操作其实是个固定流程:获取连接 → 创建Statement → 执行SQL → 处理结果集 → 关闭资源。JdbcTemplate把「处理结果集」这一步抽象出来,让调用者自己实现。
// JdbcTemplate的模板方法模式
public class JdbcTemplate {
public <T> T query(String sql, ResultSetExtractor<T> rse) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 固定步骤:获取连接
conn = getConnection();
// 固定步骤:创建Statement
stmt = conn.createStatement();
// 固定步骤:执行SQL
rs = stmt.executeQuery(sql);
// 可变步骤:处理结果集(由回调实现)
return rse.extractData(rs);
} catch (SQLException e) {
throw new DataAccessException(e);
} finally {
// 固定步骤:关闭资源
closeResultSet(rs);
closeStatement(stmt);
closeConnection(conn);
}
}
}
// 使用示例
List<User> users = jdbcTemplate.query(
"SELECT * FROM user WHERE age > ?",
new Object[]{18},
(rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
}
);
核心思想:模板方法模式把「不变的部分」封装在父类,把「变的部分」通过回调或抽象方法暴露出去。这样既保证了流程的规范性,又保留了灵活性。
知识体系总览
下面这张图,是我梳理的Spring框架中设计模式的应用全景。你可以把它当作一个速查表。
说实话,Spring框架之所以能成为Java生态的标杆,就是因为它把设计模式用到了极致。我们平时写代码,不用刻意去套用设计模式,但理解这些模式在Spring中的应用,能帮我们写出更优雅、更可维护的代码。
最后分享一个我的习惯:每当我需要设计一个公共组件时,我会先问自己三个问题:
- 这个组件的「不变流程」是什么?(模板方法模式)
- 哪些部分需要让调用者自定义?(策略模式/回调)
- 如何降低组件间的耦合?(中介者模式/观察者模式)
想清楚这三点,设计出来的组件基本不会太差。
公众号:蓝海资料掘金营,微信deep3321