概述
本文是《Spring 核心容器与扩展机制》系列的第 11 篇。前文我们深度探讨了 IoC 容器、Bean 生命周期、依赖注入、AOP、循环依赖、SpEL、容器扩展点、@Import 机制以及类型转换与数据绑定体系,目前为止已积累了关于 Spring 运作机理的丰富“术”。本文将视角从“术”升维至“道”——提炼支撑 Spring 框架灵活、健壮、高度可扩展的二十余种设计模式,揭示其架构设计的思想骨架。
设计模式是 Spring 框架的灵魂骨架。早在 Rod Johnson 划时代的著作《Expert One-on-One J2EE Development without EJB》中,设计模式就已经融入了框架的基因:IoC 容器是一个庞大的抽象工厂与单例注册表,AOP 是代理模式与责任链模式的完美合奏,而经典的 refresh() 方法更是模板方法模式的巅峰演示。本文将逐一剖析这些模式在 Spring 5.x 源码中的具体落地,让抽象的模式概念变成可触摸的代码,帮助读者真正获得“架构思维”的跃迁。
核心要点一览:
- 容器与模式:
BeanFactory体现了抽象工厂和单例模式;refresh()的骨架是模板方法的典范。 - AOP 与模式:代理模式是核心,拦截器链是责任链,通知适配用到了适配器模式。
- 事件与模式:观察者模式贯穿容器生命周期,支撑 Spring 的事件驱动编程模型。
- Web 与模式:
HandlerAdapter是适配器模式的绝佳应用,将多种 Handler 统一调度。 - 数据转换与模式:
ConversionService的策略模式使转换器可自由扩展,实现开放-闭合原则。 - 设计模式的协作:Spring 的成功不仅在于使用了单个模式,更在于让各个模式有机协作,形成了一个高度可扩展的生态。
1. 设计模式总览:Spring 中的模式分布地图
1.1 本文的组织结构
本文遵循“模式总览 → 创造者模式 → 结构型模式 → 行为型模式 → 协作分析 → 生产事故 → 面试实战”的认知路径,共计 7 大模块:
flowchart TD
A["(1) 设计模式总览<br/>模式分布地图与架构图"] --> B["(2) 创造者模式<br/>工厂 · 单例 · 建造者"]
A --> C["(3) 结构型模式<br/>代理 · 适配器 · 装饰器"]
A --> D["(4) 行为型模式<br/>模板方法 · 观察者 · 策略 · 责任链 · 解释器"]
B --> E["(5) 设计模式的协作<br/>一个 Web 请求穿越 Spring 的全链路"]
C --> E
D --> E
E --> F["(6) 生产事故排查专题<br/>模式误用案例剖析"]
E --> G["(7) 面试高频专题<br/>15+ 题深度问答"]
图表主旨概括:该流程图清晰地展示了从全局设计模式地图,到创造型、结构型、行为型三大类模式的深挖,进而通过全链路协作将孤立模式串联,最终以事故排查与面试实战收官的逻辑主线。数字标号与文章模块严格对应,形成“分-合-战”的认知路径。
逐元素分解:
- 模块 (1) 提供 Spring 各模块的设计模式分布全景图,建立宏观认知;
- 模块 (2)~(4) 分别深挖创造者、结构型、行为型模式的核心实现及源码,其中行为型模式额外补充了解释器模式在 SpEL 中的体现;
- 模块 (5) 将零散的模式串联成“一个请求穿越 Spring 容器”的完整链条,搭配全链路时序图,展现模式间的严密协作;
- 模块 (6) 从反面强化,通过两个典型生产事故(含日志/堆栈示例)加深对模式边界与契约的理解;
- 模块 (7) 直面面试场景,以 15 道高频题锤炼读者体系化表达能力,包含系统设计题并对照 Spring 设计思想。
设计原理映射:这种“先拆解、再整合、后验证”的结构本身就是一种认知模式。它模仿了 Spring 自身“扩展点分离 + 组合协作”的设计哲学:先理解单个模式的职责边界,再观察它们如何通过接口和事件形成有机整体。
工程联系与关键结论:掌握设计模式在 Spring 中的具体落地,不仅有助于读懂源码,更能直接指导日常架构决策——例如如何利用模板方法设计不可变的流程骨架,如何借助观察者模式解耦状态同步,如何在复杂场景中选择合适的结构型模式实现功能增强。这些知识将让开发者在框架扩展和自定义组件时游刃有余。
1.2 Spring 设计模式全景地图
flowchart TB
subgraph A["IoC 容器"]
direction LR
A1["🏭 工厂/抽象工厂<br/>BeanFactory / ApplicationContext"]
A2["🔒 单例<br/>DefaultSingletonBeanRegistry"]
A3["🛠️ 建造者<br/>BeanDefinitionBuilder"]
A4["📋 模板方法<br/>AbstractApplicationContext.refresh"]
end
subgraph B["AOP"]
direction LR
B1["🎭 代理<br/>JdkDynamicAopProxy / CglibAopProxy"]
B2["⛓️ 责任链<br/>ReflectiveMethodInvocation"]
B3["🔌 适配器<br/>AdvisorAdapter"]
end
subgraph C["Web"]
direction LR
C1["🔌 适配器<br/>HandlerAdapter"]
C2["⛓️ 责任链<br/>HandlerExecutionChain"]
C3["🧩 装饰器<br/>ContentCachingRequestWrapper"]
end
subgraph D["事件与数据"]
direction LR
D1["👀 观察者<br/>ApplicationListener / EventMulticaster"]
D2["⚙️ 策略<br/>ConversionService / ResourceLoader"]
D3["📜 解释器<br/>SpEL ExpressionParser"]
end
A --> B
A --> C
A --> D
B --> C
图表主旨概括:全景地图以 Spring 的四大核心模块为边界,用颜色与图标区分创造型(🏭🔒🛠️)、结构型(🎭🔌🧩)和行为型(📋⛓️👀⚙️📜)模式,揭示各模块中使用最频繁的设计模式及其对应的核心类 / 接口。
逐元素分解:
- IoC 容器是 Spring 的基石,集中了工厂、单例、建造者、模板方法等模式,负责 Bean 的定义、创建、配置与生命周期管理。
BeanFactory作为抽象工厂是入口,DefaultSingletonBeanRegistry以单例缓存保证性能,BeanDefinitionBuilder简化复杂元数据构建,refresh()则用模板方法统一了容器的启动流程。 - AOP 以代理模式为核心,由
JdkDynamicAopProxy/CglibAopProxy生成代理对象,再通过ReflectiveMethodInvocation的责任链执行拦截器;同时AdvisorAdapter作为适配器将各类通知统一为拦截器。 - Web 中
HandlerAdapter适配不同 Controller 实现,HandlerExecutionChain将多个HandlerInterceptor组成责任链,而ContentCachingRequestWrapper等装饰器则在不改变接口的前提下增强了 HTTP 请求的能力。 - 事件与数据 模块利用观察者模式实现事件广播与监听,利用策略模式让类型转换和资源加载可无限扩展,同时 SpEL 使用解释器模式来解析和执行表达式。
设计原理映射:每个模块的模式选择都有其必然性。例如 IoC 容器管理成千上万的 Bean,工厂模式抽象了创建过程,单例模式缓存无状态对象以提升性能;AOP 需要透明地在方法前后添加增强,代理模式就是最自然的结构型方案;而面对多种 Controller 风格,DispatcherServlet 当然会采用适配器模式避免自身臃肿。
工程联系与关键结论:当开发者在 Spring 上进行扩展时,本质上就是在这些模式预留的扩展点上编写自定义实现。例如,想要介入 Bean 的创建过程,就去找工厂模式的钩子(BeanPostProcessor);想要添加全局拦截,就利用 AOP 的责任链或 Web 的拦截器链;想要监听容器事件,就实现观察者接口。理解全景地图,就等于拿到了 Spring 的“藏宝图”。
2. 创造者模式:工厂、单例、建造者
2.1 工厂 / 抽象工厂 —— BeanFactory 体系
模式意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。抽象工厂模式则提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
Spring 中的核心实现:
org.springframework.beans.factory.BeanFactory—— 工厂方法 / 抽象工厂根接口。org.springframework.context.ApplicationContext—— 更高级的抽象工厂,提供丰富的企业级功能。org.springframework.beans.factory.support.DefaultListableBeanFactory—— 最完整的工厂实现,既注册 Bean 定义,又生产 Bean 实例。
类图——工厂与单例模式集成:
classDiagram
class BeanFactory {
<<interface>>
+getBean(String name) Object
+getBean(Class requiredType) Object
+containsBean(String name) boolean
}
class ApplicationContext {
<<interface>>
+getEnvironment() ConfigurableEnvironment
+publishEvent(ApplicationEvent event)
}
class ConfigurableListableBeanFactory {
<<interface>>
+getBeanDefinition(String beanName) BeanDefinition
+getBeansOfType(Class type) Map~String~
}
class DefaultListableBeanFactory {
-beanDefinitionMap: Map~String~
+preInstantiateSingletons()
}
class SingletonBeanRegistry {
<<interface>>
+getSingleton(String beanName) Object
+registerSingleton(String beanName Object singletonObject)
}
class DefaultSingletonBeanRegistry {
-singletonObjects: Map~String~
+getSingleton(String beanName boolean allowEarlyReference) Object
}
BeanFactory <|-- ApplicationContext
BeanFactory <|-- ConfigurableListableBeanFactory
ConfigurableListableBeanFactory <|-- DefaultListableBeanFactory
SingletonBeanRegistry <|-- DefaultSingletonBeanRegistry
DefaultListableBeanFactory --|> DefaultSingletonBeanRegistry
图表主旨概括:该图精确地描述了 Spring 容器如何将抽象工厂、工厂方法和单例注册表三种模式融为一体。DefaultListableBeanFactory 是最终的集大成者,它既是能生产各类 Bean 的抽象工厂,又通过继承 DefaultSingletonBeanRegistry 获得了单例缓存能力。
逐元素分解:
BeanFactory是工厂方法模式的根接口,只定义了“获取 Bean”的最基本操作,而不关心 Bean 的来源。ApplicationContext扩展了BeanFactory,提供了环境抽象、消息源、事件发布等企业级服务,形成更丰富的抽象工厂。ConfigurableListableBeanFactory汇总了配置和枚举型工厂接口,包含了获取所有 Bean 定义、提前实例化单例等能力。DefaultListableBeanFactory是实际干活的工厂,内部维护一个beanDefinitionMap,并提供registerBeanDefinition方法来登记定义。- 右侧分支的
SingletonBeanRegistry与DefaultSingletonBeanRegistry则专门处理单例对象的生产、缓存与查找,并通过三级缓存巧妙地解决循环依赖。
设计原理映射:Spring 将“Bean 的定义(BeanDefinition)”与“Bean 的实例”彻底分离,工厂负责根据定义生产实例,单例注册表负责缓存实例。这种分离使我们可以通过 BeanFactoryPostProcessor 修改定义,或通过 BeanPostProcessor 修改实例,而无需改变工厂的核心代码,是“开闭原则”的完美体现。
工程联系与关键结论:所有自定义 Bean 后处理器(BeanPostProcessor)和工厂后处理器(BeanFactoryPostProcessor)都依赖此体系。当我们需要动态注册 Bean 或修改已注册 Bean 的构造方式时,直接操作 DefaultListableBeanFactory 的 API 或实现相应的后处理器接口即可。
2.1.1 源码分析:getBean 中的工厂与模板方法协作
// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
@Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 1. 转换 beanName,处理别名和 FactoryBean 前缀
String beanName = transformedBeanName(name);
Object sharedInstance;
// 2. 先尝试从单例缓存中获取(单例模式的体现)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 若拿到的是 FactoryBean,则调用 getObject 获取真正对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
return (T) bean;
}
// 3. 检查原型模式下是否存在循环依赖
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 4. 若当前工厂没有该 Bean 定义,则委托给父工厂(工厂链模式)
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// ... 委托调用 parentBeanFactory.getBean()
}
// 5. 标记该 bean 正在创建,合并 BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 6. 处理 depends-on 声明的依赖
// ...
// 7. 根据作用域创建 bean 实例
if (mbd.isSingleton()) {
// 使用 getSingleton 的重载版本,传入 ObjectFactory 作为创建回调(模板方法思想)
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args);
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// 原型:每次调用 createBean
Object prototypeInstance = createBean(beanName, mbd, args);
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
// 其他作用域(request、session 等)
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(scopeName, () -> createBean(beanName, mbd, args));
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
return (T) bean;
}
解读:doGetBean 方法既是工厂模式的入口,也运用了模板方法模式。它定义了“获取 Bean 的标准流程”(检查单例缓存 → 检查循环依赖 → 委托父工厂 → 创建新实例),但将真正创建 Bean 的细节留在 createBean(由子类实现)和传入的 lambda 中。这种设计使创建流程稳定且可预测,同时提供了极大的扩展弹性。
2.2 单例 —— DefaultSingletonBeanRegistry
模式意图:确保一个类只有一个实例,并提供一个全局访问点。Spring 中的“单例”并不是 GoF 意义上的 JVM 级唯一静态实例,而是容器级别的唯一——保证一个 ApplicationContext 内,同一 beanName 对应的 Bean 只有一个共享实例。
Spring 中的核心实现:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry,其三级缓存singletonObjects(一级)、earlySingletonObjects(二级)、singletonFactories(三级)是单例管理的核心,同时也解决了循环依赖(详见前文第 5 篇)。
作用与扩展:Spring 的单例机制不仅保证了默认作用域下 Bean 的唯一性,还通过 ObjectFactory 等接口支持延迟创建和早期暴露,为框架的并发性能与扩展性提供坚实基础。
2.2.1 源码分析:三级缓存与单例注册
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry
implements SingletonBeanRegistry {
/** 一级缓存:完全初始化好的单例对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:早期暴露的对象(尚未完成属性填充),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** 三级缓存:单例对象的创建工厂 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 获取单例(允许早期引用)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 从三级缓存升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
// 带创建回调的单例获取方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (BeanCreationException ex) {
throw ex;
} finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject); // 加入一级缓存
}
}
return singletonObject;
}
}
}
解读:Spring 对单例的实现绝非简单的 static final 变量。它通过 synchronized 保证并发写入安全,用三级缓存允许还未完全装配的 Bean 被提前暴露(仅用于循环依赖场景),而 ObjectFactory 则延迟了对象的产生时机。这是经典单例模式在容器级的一次重要升华,实现了“类级唯一”到“容器内唯一”的语义扩展。
2.3 建造者 —— BeanDefinitionBuilder
模式意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Spring 中的核心实现:
org.springframework.beans.factory.support.BeanDefinitionBuilder提供了流式 API,用于以编程方式构建BeanDefinition(特别是AbstractBeanDefinition的各种子类),避免手动new GenericBeanDefinition()并逐行 set 各个属性。
作用:在框架内部(如解析 @Bean 注解、处理 @Import 时)以及开发者进行动态注册时,建造者模式让代码更简洁、意图更清晰,且容易维护。
2.3.1 建造者流式 API 示例与源码分析
// org.springframework.beans.factory.support.BeanDefinitionBuilder
public final class BeanDefinitionBuilder {
private AbstractBeanDefinition beanDefinition;
// 静态工厂方法,返回建造者实例
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
// 流式设置属性,每个方法返回 BeanDefinitionBuilder 自身
public BeanDefinitionBuilder addPropertyValue(String name, Object value) {
this.beanDefinition.getPropertyValues().add(name, value);
return this;
}
public BeanDefinitionBuilder addPropertyReference(String name, String beanName) {
this.beanDefinition.getPropertyValues().add(name, new RuntimeBeanReference(beanName));
return this;
}
public BeanDefinitionBuilder setLazyInit(boolean lazyInit) {
this.beanDefinition.setLazyInit(lazyInit);
return this;
}
public BeanDefinitionBuilder setScope(String scope) {
this.beanDefinition.setScope(scope);
return this;
}
// 获取最终 product
public AbstractBeanDefinition getBeanDefinition() {
return this.beanDefinition;
}
}
内联示例:手动使用建造者注册一个数据源 Bean:
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(HikariDataSource.class)
.addPropertyValue("jdbcUrl", "jdbc:mysql://localhost:3306/mydb")
.addPropertyValue("username", "root")
.addPropertyValue("password", "secret")
.addPropertyValue("maximumPoolSize", 20)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setLazyInit(false);
// 将 BeanDefinition 注册到容器(前提是获取到 BeanDefinitionRegistry)
((BeanDefinitionRegistry) applicationContext)
.registerBeanDefinition("dataSource", builder.getBeanDefinition());
设计优势:建造者模式避免了大量的 setter 调用和构造器重载,构建意图一目了然。同时,它把构建逻辑与最终的使用分开,便于在构建过程中插入校验、默认值等逻辑。
2.4 创造者模式小结
三种模式共同构建了 Spring 的对象生产流水线:工厂负责调度和创建,单例负责缓存与复用,建造者则优雅地组装复杂对象的定义。它们与 Spring 的扩展点(如 BeanFactoryPostProcessor、BeanPostProcessor)紧密相连,理解这些模式就能精准找到 Bean 创建与注册的切入位置。
3. 结构型模式:代理、适配、装饰
3.1 代理 —— AOP 的基石
模式意图:为其他对象提供一种代理以控制对这个对象的访问。Spring AOP 使用代理透明地织入切面,是对代理模式最经典的工程应用。
Spring 中核心实现:
org.springframework.aop.framework.JdkDynamicAopProxy—— 基于 JDK 动态代理(目标实现了接口)。org.springframework.aop.framework.CglibAopProxy—— 基于 CGLIB 子类代理(目标未实现接口或强制使用 CGLIB)。
作用:在不修改原始类的前提下,为其动态添加横切关注点(日志、事务等),实现 AOP 承诺的“无侵入”增强。详见前文第 4 篇《AOP 深度剖析》。
3.1.1 源码片段:JdkDynamicAopProxy 的代理创建与调用
// org.springframework.aop.framework.JdkDynamicAopProxy#getProxy
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
// 获取目标类实现的所有接口(包括 Spring 引入接口)
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 调用 JDK 标准 API 创建代理,this 即为 InvocationHandler
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
// JdkDynamicAopProxy 实现了 InvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ... 省略 target 校验
// 获取拦截器链(责任链的准备工作)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 如果链为空,直接通过反射调用目标方法
if (chain.isEmpty()) {
Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
return retVal;
}
// 否则,创建方法调用对象并启动责任链
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
return invocation.proceed();
}
解读:JdkDynamicAopProxy 自己就是 InvocationHandler,在代理实例上任何方法调用都会被路由到 invoke 方法。这里它没有直接执行业务逻辑,而是先组装一个拦截器链,再交给 ReflectiveMethodInvocation 的责任链去逐一调用。这种设计体现了代理模式与责任链模式的天然协同:代理负责截获请求,责任链负责组织增强的顺序。
3.2 适配器 —— 统一的入口
模式意图:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。
Spring 中的两个典型应用:
- AOP 通知适配:
org.springframework.aop.framework.adapter.AdvisorAdapter将不同类型的Advice(MethodBeforeAdvice、AfterReturningAdvice等)统一适配为MethodInterceptor。 - MVC Handler 适配:
org.springframework.web.servlet.HandlerAdapter让DispatcherServlet能调用各种形式的 Handler(@Controller、HttpRequestHandler、Servlet等)。
类图——MVC 适配器模式:
classDiagram
class DispatcherServlet {
+doDispatch(HttpServletRequest, HttpServletResponse)
}
class HandlerAdapter {
<<interface>>
+supports(Object handler) boolean
+handle(HttpServletRequest, HttpServletResponse, Object handler) ModelAndView
}
class RequestMappingHandlerAdapter {
+handleInternal(HttpServletRequest, HttpServletResponse, HandlerMethod)
}
class SimpleControllerHandlerAdapter {
+handle(HttpServletRequest, HttpServletResponse, Object handler)
}
class HttpRequestHandlerAdapter {
+handle(HttpServletRequest, HttpServletResponse, Object handler)
}
DispatcherServlet --> HandlerAdapter : 使用
HandlerAdapter <|.. RequestMappingHandlerAdapter
HandlerAdapter <|.. SimpleControllerHandlerAdapter
HandlerAdapter <|.. HttpRequestHandlerAdapter
图表主旨概括:DispatcherServlet 作为前端控制器,只依赖 HandlerAdapter 接口,不同的适配器实现负责处理不同种类的 Controller(或 Handler)。这种架构允许 Spring MVC 在不改变核心调度逻辑的前提下,支持任意新的 Handler 类型。
逐元素分解:
DispatcherServlet在doDispatch中先通过HandlerMapping获得 Handler 对象,然后调用getHandlerAdapter找到支持该 Handler 的适配器。HandlerAdapter定义了两个关键方法:supports(handler)判断是否能处理,handle(req, resp, handler)执行并返回ModelAndView。RequestMappingHandlerAdapter专门处理HandlerMethod(通常由@RequestMapping注解生成);SimpleControllerHandlerAdapter处理传统的Controller接口实现;HttpRequestHandlerAdapter处理直接操作HttpServletRequest的HttpRequestHandler。
设计原理映射:适配器模式完美隔离了“识别 Handler 类型”和“执行 Handler”的职责,使得 DispatcherServlet 完全不依赖具体的 Handler 实例类型。新增一种 Handler 类型,只需提供对应的 HandlerAdapter 并注册即可,完全遵循开闭原则。
工程联系与关键结论:在 Spring MVC 中,自定义的返回值处理器(HandlerMethodReturnValueHandler)或参数解析器(HandlerMethodArgumentResolver)本质上也是在扩展 RequestMappingHandlerAdapter 的能力,是适配器模式内部的进一步微适配。理解这一点能帮助开发者在遇到参数绑定失败时,迅速定位到对应的解析策略。
3.2.1 源码片段:适配器的选择与执行
// org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter" +
" that supports this handler");
}
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod)
throws Exception {
ModelAndView mav;
// 检查请求方法是否支持,处理 session 同步等...
mav = invokeHandlerMethod(request, response, handlerMethod);
// ... 后续处理
return mav;
}
解读:getHandlerAdapter 通过简单的 for 循环 + supports 完成适配器的选择,保证了扩展点的高度灵活性。而 handleInternal 内部则进一步使用策略模式调用参数解析器、返回值处理器,形成了“适配器套策略”的复合结构。
3.3 装饰器 —— 增强而非改变
模式意图:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
Spring 中的典型实例:
org.springframework.beans.factory.xml.BeanDefinitionDecorator—— 在 XML 解析过程中装饰 Bean 定义(如<aop:scoped-proxy>)。- Web 层请求装饰器:如
org.springframework.web.util.ContentCachingRequestWrapper包装原始的HttpServletRequest,为其添加缓存请求体的能力,广泛用于日志记录和调试。 org.springframework.http.server.reactive.ServerHttpRequestDecorator—— WebFlux 侧对请求的装饰基类。
3.3.1 源码片段:ContentCachingRequestWrapper 的装饰
// org.springframework.web.util.ContentCachingRequestWrapper
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private final ByteArrayOutputStream cachedContent = new ByteArrayOutputStream();
@Override
public ServletInputStream getInputStream() throws IOException {
if (this.cachedContent.size() > 0) {
// 已经缓存了内容,返回缓存的流
return new ContentCachingInputStream(this.cachedContent.toByteArray());
}
// 首次调用,记录原始流内容到 cachedContent
ServletInputStream is = getRequest().getInputStream();
return new ContentCachingInputStream(is, this.cachedContent);
}
public byte[] getContentAsByteArray() {
return this.cachedContent.toByteArray();
}
}
解读:ContentCachingRequestWrapper 继承自 HttpServletRequestWrapper(它本身就是 HttpServletRequest 的装饰器),在保持原有请求接口完全不变的前提下,增加了“多次读取请求体”的功能。这正是装饰器模式的精髓:不改变现有接口契约,只增加行为。
3.3.2 装饰器与代理的区别
在 Spring 中,装饰器多用于元数据或底层 I/O 对象包装(如 BeanDefinitionDecorator、ServerHttpRequestDecorator),关注的是功能增强;而代理(JdkDynamicAopProxy)则侧重于控制访问并可能修改行为,通常伴随着切面逻辑的织入。二者虽然结构相似,但意图上有明显差异。
3.4 结构型模式小结
代理模式保障了 AOP 的透明织入;适配器模式统一了多态 Handler 和通知的调用接口;装饰器模式则在解析与 Web 层灵活增强对象能力。三者组合让 Spring 能够以统一、可扩展的方式处理高度异构的组件,是框架“松耦合”哲学的体现。
4. 行为型模式:模板方法、观察者、策略、责任链、解释器
4.1 模板方法 —— AbstractApplicationContext.refresh()
模式意图:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Spring 中的巅峰实现:org.springframework.context.support.AbstractApplicationContext#refresh() 方法是整个 Spring 容器初始化的核心骨架,它为所有 ApplicationContext 实现(ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、SpringBoot 内置容器等)定义了统一的 12 步启动流程,同时提供了多个可供重写的钩子方法。
序列图——模板方法刷新流程:
sequenceDiagram
participant Client
participant AbstractApplicationContext
participant GenericApplicationContext
participant DefaultListableBeanFactory
Client->>AbstractApplicationContext: refresh()
AbstractApplicationContext->>AbstractApplicationContext: 1. prepareRefresh()
AbstractApplicationContext->>AbstractApplicationContext: 2. obtainFreshBeanFactory()
AbstractApplicationContext->>GenericApplicationContext: refreshBeanFactory()
GenericApplicationContext-->>AbstractApplicationContext: 返回 BeanFactory
AbstractApplicationContext->>AbstractApplicationContext: 3. prepareBeanFactory(bf)
AbstractApplicationContext->>AbstractApplicationContext: 4. postProcessBeanFactory(bf) [钩子]
AbstractApplicationContext->>AbstractApplicationContext: 5. invokeBeanFactoryPostProcessors(bf)
AbstractApplicationContext->>AbstractApplicationContext: 6. registerBeanPostProcessors(bf)
AbstractApplicationContext->>AbstractApplicationContext: 7. initMessageSource()
AbstractApplicationContext->>AbstractApplicationContext: 8. initApplicationEventMulticaster()
AbstractApplicationContext->>AbstractApplicationContext: 9. onRefresh() [钩子]
AbstractApplicationContext->>AbstractApplicationContext: 10. registerListeners()
AbstractApplicationContext->>DefaultListableBeanFactory: 11. finishBeanFactoryInitialization(bf)
AbstractApplicationContext->>AbstractApplicationContext: 12. finishRefresh()
AbstractApplicationContext-->>Client: 容器就绪
图表主旨概括:通过序列图细致地展示了 refresh() 方法的 12 个标准步骤及其调用顺序,明确标出钩子方法(4、9)供子类扩展,同时也展示了核心 BeanFactory 初始化和单例预实例化的时机。
逐元素分解:
- 步骤 1-3 为准备阶段:设置启动时间、环境变量,创建并配置基础的 BeanFactory。
- 步骤 4 和 9 是关键的钩子方法,
postProcessBeanFactory允许在标准配置完成后对 BeanFactory 进行额外定制,onRefresh则留给子类做与 Bean 无关的初始化(例如启动 Web 服务器)。 - 步骤 5-6 调用各种扩展点接口(
BeanFactoryPostProcessor、BeanPostProcessor),为后续 Bean 的创建铺平道路。 - 步骤 8 和 10 初始化事件广播器并注册监听器,开始建立观察者模式的基础设施。
- 步骤 11 是所有非懒加载单例 Bean 的实例化高峰,步骤 12 完成刷新并发布
ContextRefreshedEvent。
设计原理映射:这种骨架设计将不变的主流程(步骤顺序)与可变的子步骤分离,完美体现了“好莱坞原则”——“别调用我们,我们会调用你”。开发人员只需重写钩子方法或实现相应扩展接口,就能在不触碰核心代码的前提下定制容器启动行为。
工程联系与关键结论:在 Spring Boot 中,ServletWebServerApplicationContext 正是通过重写 onRefresh() 来启动内嵌的 Web 服务器。当我们自定义容器启动逻辑时,首选不是重写 refresh(),而是利用 ApplicationListener 或 ApplicationRunner,因为它们是在容器完全就绪后才被调用的,更安全。
4.1.1 源码片段:refresh() 骨架的详细注释
// org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新上下文,记录启动时间,初始化属性源
prepareRefresh();
// 2. 获取新的 BeanFactory,不同子类可以返回不同的实现(XML、注解)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 给 BeanFactory 装配标准组件(类加载器、EL 解析器等)
prepareBeanFactory(beanFactory);
try {
// 4. 提供一个钩子,允许子类在 BeanFactory 标准配置后添加自己的属性
postProcessBeanFactory(beanFactory);
// 5. 调用所有注册的 BeanFactoryPostProcessor(可以修改 Bean 定义)
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 BeanPostProcessor,拦截 Bean 的创建
registerBeanPostProcessors(beanFactory);
// 7. 初始化国际化消息资源
initMessageSource();
// 8. 初始化事件广播器(观察者模式的核心组件)
initApplicationEventMulticaster();
// 9. 留给子类的刷新钩子(例如 Spring Boot 在此启动 Web 服务器)
onRefresh();
// 10. 注册所有 ApplicationListener
registerListeners();
// 11. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新,发布 ContextRefreshedEvent 事件
finishRefresh();
} catch (BeansException ex) {
// 出错时销毁所有已创建的 Bean
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}
4.2 观察者 —— 容器事件机制
模式意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Spring 中的核心组件:
org.springframework.context.ApplicationEvent—— 抽象事件基类。org.springframework.context.ApplicationListener<E>—— 监听器接口。org.springframework.context.event.ApplicationEventMulticaster—— 事件广播器(默认实现SimpleApplicationEventMulticaster)。@EventListener注解 +EventListenerMethodProcessor后处理器,将注解方法动态注册为监听器。
序列图——事件广播流程:
sequenceDiagram
participant Publisher
participant ApplicationEventMulticaster
participant Listener1
participant Listener2
Publisher->>ApplicationEventMulticaster: multicastEvent(ContextRefreshedEvent)
ApplicationEventMulticaster->>ApplicationEventMulticaster: 获取匹配的监听器列表
loop 遍历所有监听器
ApplicationEventMulticaster->>Listener1: onApplicationEvent(event)
ApplicationEventMulticaster->>Listener2: onApplicationEvent(event)
end
图表主旨概括:事件发布者通过 ApplicationEventMulticaster 将事件广播给所有已注册的监听器,发布者无需知道谁在监听,监听者也无需知道事件来源,实现了完全的发布-订阅解耦。
逐元素分解:
- 发布者通常是
ApplicationContext本身(通过publishEvent方法),任何 Bean 只要持有ApplicationEventPublisher引用也可以发布。 ApplicationEventMulticaster负责管理监听器列表,在收到事件后遍历匹配的监听器并调用其onApplicationEvent。- 监听器可以基于泛型指定感兴趣的事件类型,广播器会自动过滤。
设计原理映射:观察者模式让状态变化的通知与业务处理分离开来。Spring 容器自身也大量使用事件来通知容器生命周期的各个阶段(如 ContextRefreshedEvent、ContextClosedEvent),开发者可以极低的成本介入这些阶段。
工程联系与关键结论:当需要监听容器完全初始化完毕或即将销毁时,直接实现 ApplicationListener 或在方法上标注 @EventListener,且可结合 @Async 实现异步处理。但需注意异步事件的异常不会影响主流程,必须自行捕获处理。
4.2.1 源码片段:事件广播的实现
// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取线程池(若设置则可异步执行)
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
内联示例:自定义监听器观察容器事件:
@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 此时容器已完全就绪,可以执行安全的后置初始化操作
System.out.println("容器刷新完毕,ConfigurableApplicationContext: " + event.getSource());
}
}
4.3 策略 —— ConversionService 与 ResourceLoader
模式意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而变化。
Spring 中的两个典范:
- 类型转换策略:
org.springframework.core.convert.ConversionService接口作为策略的执行者,内部根据源类型和目标类型动态选择匹配的Converter/GenericConverter策略。 - 资源加载策略:
org.springframework.core.io.ResourceLoader针对不同的路径前缀(classpath:、file:、http:等)使用不同的策略返回对应的Resource实现。
类图——转换服务策略匹配:
classDiagram
class ConversionService {
<<interface>>
+convert(Object source Class targetType) Object
+canConvert(Class sourceType Class targetType) boolean
}
class GenericConversionService {
-converters: Set
+addConverter(Converter converter)
+convert(Object source TypeDescriptor TypeDescriptor) Object
}
class Converter {
<<interface>>
+convert(S source) T
}
class GenericConverter {
<<interface>>
+convert(Object source TypeDescriptor TypeDescriptor) Object
+getConvertibleTypes() Set
}
GenericConversionService --> Converter
GenericConversionService --> GenericConverter
ConversionService <|.. GenericConversionService
图表主旨概括:GenericConversionService 内部维护了一组转换器(Converter 和 GenericConverter 实现),它根据源类型和目标类型从集合中选取第一个匹配的转换器执行,完美对应策略模式。
逐元素分解:
ConversionService定义高层接口,canConvert和convert是典型的策略执行入口。GenericConversionService是 Spring 提供的通用实现,其converters字段持有所有注册的策略。Converter<S,T>是最简单的策略接口,适合一对一转换;GenericConverter则能处理更复杂的类型对。- 当执行
convert时,通过内部的getConverter查找一个匹配的转换器并调用。
设计原理映射:通过策略模式,Spring 的类型转换体系变得极易扩展。不管是内置的 String -> Date 转换,还是自定义的 String -> Money 转换,只需实现 Converter 并注册到 ConversionService 中,无需修改任何现有逻辑,完全遵循开闭原则。
工程联系与关键结论:在 Spring MVC 的请求参数绑定中,WebDataBinder 就是利用 ConversionService 的策略来将 HTTP 文本参数转换为 Controller 方法的参数类型。当参数绑定失败时,检查是否缺少对应的 Converter 往往是排错的第一步。
4.3.1 源码片段:convert 方法的选择逻辑
// org.springframework.core.convert.support.GenericConversionService#convert
@Override
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
// 省略快速路径和空值处理
// 从注册的转换器中查找匹配的策略
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return result;
}
// 若没有直接匹配,尝试通过复合转换器处理(策略链)
return handleConverterNotFound(source, sourceType, targetType);
}
补充说明 ResourceLoader 策略:
DefaultResourceLoader 的 getResource(String location) 方法中,依据路径前缀匹配不同的资源实现,例如 classpath:xxx 返回 ClassPathResource,file:xxx 返回 FileUrlResource,这也是典型的策略模式应用。后期 Spring Boot 还会进一步装饰 ResourceLoader 来支持 Fat Jar 内的资源,展现了策略与装饰模式的组合。
4.4 责任链 —— 拦截器链与 AOP 链
模式意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止(或在 Spring AOP 中通常是所有链节都处理)。
Spring 中的典型应用:
- AOP 拦截器链:
MethodInterceptor链通过ReflectiveMethodInvocation.proceed()实现递归调用。 - Web 过滤器链:Servlet 规范的
FilterChain,Spring Security 的SecurityFilterChain。 - Spring MVC 拦截器链:
HandlerExecutionChain包装HandlerInterceptor集合,在applyPreHandle、applyPostHandle中顺序调用。
4.4.1 源码片段:AOP 链 ReflectiveMethodInvocation.proceed()
// org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
@Override
public Object proceed() throws Throwable {
// 当拦截器索引到达最大值,所有拦截器已执行完毕,调用实际目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 取出下一个拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
// 动态方法匹配器:检查当前调用是否匹配应用条件
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
} else {
// 不匹配则递归调用自身,执行下一个拦截器
return proceed();
}
} else {
// 普通拦截器,直接调用 invoke 并把自身传递进去
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
解读:每个 MethodInterceptor 的 invoke 方法收到 MethodInvocation 对象,可以在调用 invocation.proceed() 之前、之后添加增强,或者完全阻止链的传递。这种递归结构是责任链的经典实现,整个链条没有中心调度器,控制流分散在各个拦截器中。
4.4.2 Web 层拦截器链的协同
除了 AOP,Spring MVC 中的 HandlerExecutionChain 也采用责任链模式,将多个 HandlerInterceptor 顺序执行:
// org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptors.length; i++) {
HandlerInterceptor interceptor = this.interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false; // 任意一个拦截器返回 false,链终止
}
}
return true;
}
与 AOP 链的差异:Web 拦截器链是顺序遍历,遇到 preHandle 返回 false 即可提前中断整条链,而 AOP 链的每个拦截器自行决定是否继续调用 proceed。但二者本质上都是责任链模式的不同变体。
4.5 解释器 —— SpEL 表达式解析
模式意图:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
Spring 中的体现:
Spring 表达式语言(SpEL)通过 org.springframework.expression.ExpressionParser 和 org.springframework.expression.spel.standard.SpelExpressionParser 实现解释器模式。它将字符串表达式解析成一个抽象语法树(AST),并能在运行时通过 Expression.getValue() 求值。
示例对照:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue(); // 解释执行
尽管 SpEL 不是本文重点(前文第 6 篇有深入探讨),但它与解释器模式的映射,进一步丰富了 Spring 的行为型模式矩阵。
4.6 行为型模式小结
模板方法构建容器启动的固定骨架;观察者解耦状态变化与业务响应;策略模式让类型转换和资源加载可无限扩展;责任链编织了 AOP 和 Web 拦截的灵活之网;解释器则支撑动态表达式的求值。这些行为型模式共同铸就了 Spring 运行时的高度动态性与扩展性。
5. 设计模式的协作:一个请求穿越 Spring 的全链路
设计模式在 Spring 中并非孤岛,它们紧密协作,共同完成一个看似简单的 Web 请求。下面我们以一个典型的 HTTP 请求为例,揭示这些模式是如何嵌套工作的。
5.1 全链路协作图
flowchart LR
A["浏览器请求<br/>HTTP Request"] --> B["DispatcherServlet<br/>前端控制器"]
B -->|适配器模式| C{"HandlerAdapter<br/>选择合适的适配器"}
C --> D["RequestMappingHandlerAdapter<br/>适配 @Controller"]
D --> E["调用 HandlerMethod"]
E --> F{"工厂模式<br/>BeanFactory.getBean"}
F --> G["Service Bean<br/>(可能被 AOP 代理)"]
G -->|代理模式| H["JdkDynamicAopProxy<br/>/ CglibAopProxy"]
H -->|责任链模式| I["拦截器链<br/>ReflectiveMethodInvocation"]
I --> J["实际执行业务逻辑"]
J --> K[返回业务结果]
K --> L{"策略模式<br/>ConversionService<br/>返回值 / 参数转换"}
L --> M["HandlerMethodReturnValueHandler<br/>处理返回值"]
M --> N["视图解析 / 消息转换"]
图表主旨概括:一个 Web 请求从进入 DispatcherServlet 开始,依次经过适配器模式、工厂模式、代理模式、责任链模式、策略模式,期间还可能触发观察者事件。这幅图展示了一次请求调用中多种设计模式的无缝接力。
逐元素分解:
- 适配器:
DispatcherServlet不关心 Handler 的具体类型,通过HandlerAdapter接口适配不同的 Controller 实现。 - 工厂:Handler 在执行时需要依赖其他 Bean,通过
BeanFactory.getBean获取,可能触发创建或从单例缓存中返回,工厂和单例模式同时工作。 - 代理:如果目标 Bean 被 AOP 增强,
getBean返回的实际上是代理对象,后续方法调用将由JdkDynamicAopProxy或CglibAopProxy接管。 - 责任链:代理对象内部将所有通知封装为拦截器链,并依次递归执行。
- 策略:在参数绑定和返回值处理阶段,
ConversionService通过策略模式选择合适的Converter转换请求参数,HandlerMethodReturnValueHandler策略根据返回值类型选择处理器(如@ResponseBody使用HttpMessageConverter策略)。
设计原理映射:这种链条完美示范了“组合优于继承”和“单一职责”原则:每个模式只负责处理一种维度的变化,模式之间通过接口(如 BeanFactory、HandlerAdapter、ConversionService)衔接,最终构成一个高度协同且可扩展的处理管道。
工程联系与关键结论:当我们在调试一个请求时发现性能瓶颈或行为异常,就可以沿着这条“模式链”逐步排查:是适配器选错了导致 404?是代理产生了意外的增强?还是转换器匹配了错误的策略导致 400?这种基于模式视角的排查方法,往往能大幅提高问题定位的效率。
6. 生产事故排查专题
6.1 事故一:单例 Bean 注入原型 Bean 导致预期“每次获取新实例”失效
现象:
某订单系统,OrderService 是单例 Bean,内部依赖了一个原型作用域的 PriceCalculator。业务期望每次下单计算价格时都拿到全新的 PriceCalculator 实例,以保证计算过程中的中间状态不互相干扰。但在高并发压测下,多次计算却互相影响,监控显示多个线程拿到了同一个 PriceCalculator 实例,导致价格计算错乱。
关键日志/堆栈:
DEBUG - Creating instance of bean 'priceCalculator' (prototype)
DEBUG - Autowiring by type from bean name 'orderService' to bean named 'priceCalculator'
...(之后再无创建日志)
排查过程:
- 检查
PriceCalculator的定义,@Scope("prototype")配置无误。 - 检查
OrderService的代码:@Service public class OrderService { @Autowired private PriceCalculator priceCalculator; // 注入一次 public BigDecimal calculatePrice(Order order) { // 直接使用成员变量,每次调用都是同一个实例 return priceCalculator.calculate(order); } } - 在
PriceCalculator的构造方法中打印this.hashCode(),发现只打印了一次,且在calculatePrice()中打印的 hashCode 总是与之相同,证实注入仅发生一次。
根因分析(模式误用):
Spring 的单例 Bean 在初始化时,容器会一次性解析它的所有依赖并注入。原型 Bean 虽然在定义上是“每次获取都新建”,但注入过程发生的时机在单例 Bean 创建期间,之后该单例 Bean 持有的引用不会再改变。这本质上是混淆了“容器创建”与“使用者获取”的语义。正确的使用方式不应是直接注入原型实例,而应该注入一个能返回新实例的工厂。
解决方案:
- 方案1:使用
@Lookup注解。Spring 会通过 CGLIB 生成子类拦截该方法调用,每次返回一个新的原型实例。@Lookup public PriceCalculator getPriceCalculator() { return null; } // 方法体任意,会被代理重写 - 方案2:注入
ObjectFactory<PriceCalculator>或Provider<PriceCalculator>,在需要时调用getObject()获取新实例。 - 方案3:实现
ApplicationContextAware,通过applicationContext.getBean(PriceCalculator.class)每次手动获取。
实践教训:永远不要在单例 Bean 中直接注入原型 Bean 并长期持有引用。单例与原型作用域的协作必须引入“工厂”这一间接层,这是工厂模式与单例模式结合的基本契约。同时,可通过 @Lookup 这种轻量注入来避免对容器 API 的硬依赖。
6.2 事故二:模板方法中重写钩子导致容器启动失败
现象:
团队自定义了一个 MyApplicationContext 继承 AnnotationConfigApplicationContext,并重写了 onRefresh() 方法,在其中调用 getBean(ComplexService.class) 来提前触发业务初始化。项目启动时频繁抛出 BeanCurrentlyInCreationException 或 NullPointerException,且问题不稳定,有时能启动,有时不能。
排查过程:
- 查看自定义容器代码:
public class MyApplicationContext extends AnnotationConfigApplicationContext { @Override protected void onRefresh() { // 提前获取一个很重的业务 Service ComplexService service = getBean(ComplexService.class); service.warmUp(); } } - 分析源码执行顺序(回顾 4.1 节序列图):
onRefresh()在步骤 9 执行,而所有单例 Bean 的正式批量创建发生在步骤 11finishBeanFactoryInitialization。在步骤 9 时,某些 Bean 的依赖可能尚未注册完毕,或者正处于循环依赖的早期阶段。 - 在
onRefresh()和finishBeanFactoryInitialization方法前后分别加断点,观察到onRefresh触发了ComplexService及其依赖的DataRepository创建,而DataRepository又依赖于某个BeanPostProcessor,该处理器尚未完成注册(步骤 6 注册的BeanPostProcessor只是注册进工厂,其自身实例化可能也延迟到步骤 11)。这种跨步骤的依赖极易触发BeanCurrentlyInCreationException。
根因分析(模式误用):
开发者误解了模板方法模式中钩子方法的契约。onRefresh() 的设计本意是留给子类执行与 BeanFactory 无关的初始化(如启动 Web 服务器、注册 JMX MBean 等),而不是去获取和执行业务 Bean。重写钩子方法必须严格遵守其语义契约:只能依赖当前步骤已经准备就绪的资源。
解决方案:
- 方案1:使用
ApplicationListener<ContextRefreshedEvent>,事件在finishRefresh()末尾发布,此时所有单例 Bean 均已创建完毕。@Component public class WarmUpListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { event.getApplicationContext().getBean(ComplexService.class).warmUp(); } } - 方案2:在
ComplexService自身实现InitializingBean或@PostConstruct,将预热逻辑放在该 Bean 的初始化回调中。 - 方案3:若必须使用
onRefresh(),确保不要在此处触发新的 Bean 创建,只做外部资源初始化。
实践教训:重写模板方法的钩子时,务必研读该方法在骨架中的调用时机及其上下文。Spring 已经通过事件(观察者模式)为容器完全启动提供了清晰的生命周期节点,利用事件驱动模型可以安全地解耦初始化逻辑,避免钩子滥用。
7. 面试高频专题
Q1:Spring 中使用了哪些设计模式?请举例至少 5 种。
标准回答:工厂模式(BeanFactory)、单例模式(DefaultSingletonBeanRegistry)、模板方法(AbstractApplicationContext.refresh)、代理模式(JdkDynamicAopProxy)、观察者模式(ApplicationEvent/Listener)。
追问1:单例模式与 GoF 单例的实现有何区别?
回答:Spring 单例是容器级别唯一,通过三级缓存和同步锁保证,可灵活销毁与重建;GoF 单例是 JVM 级别唯一,通过静态变量和私有构造器实现。
追问2:工厂模式在 Spring 中仅仅是 BeanFactory 接口吗?
回答:还包括 FactoryBean(一种特殊的工厂)、ApplicationContext(高级抽象工厂)以及父容器委托链,形成了多层次工厂体系。
追问3:这些模式是如何协同工作的?
回答:以 AOP 为例,工厂模式创建原始 Bean,代理模式将其包装为代理,适配器将通知统一为拦截器,责任链按序调用,最终由模板方法在容器刷新时织入代理。
加分回答:Spring 还通过 BeanPostProcessor 将代理的创建时机集成进了工厂方法的生命周期中,是一种“工厂+代理”的深度绑定。
Q2:BeanFactory 用了什么设计模式?它在 Spring 中是如何体现的?
标准回答:抽象工厂模式。BeanFactory 定义获取 Bean 的接口,DefaultListableBeanFactory 等实现具体创建逻辑,且通过父子容器形成工厂链。
追问1:为什么说它是“抽象工厂”而不仅仅是工厂方法?
回答:因为它能创建一系列相关的产品(所有 Bean),并且支持不同策略的工厂实现(XML、注解),符合抽象工厂定义。
追问2:FactoryBean 和 BeanFactory 的关系?
回答:FactoryBean 是一种特殊的工厂 Bean,其本身由 BeanFactory 管理,但它又负责生产另一个对象(通过 getObject),是工厂模式在 Bean 粒度上的再次应用。
追问3:如何通过自定义 BeanFactory 实现动态数据源切换?
回答:可以自定义 AbstractRoutingDataSource(本质是策略+工厂),结合父容器和 BeanFactoryPostProcessor 动态注册数据源定义,这利用了工厂模式的扩展性和策略模式。
Q3:Spring 的单例和 GoF 的单例实现有何异同?
标准回答:相同点都是保证实例唯一。不同点在于 Spring 单例是容器内唯一,可存在多个同名但不同容器;支持延迟初始化、循环依赖解决和生命周期回调。
追问1:三级缓存除了单例,还解决了什么问题?
回答:解决了单例 Bean 之间的循环依赖,这是经典单例模式无法做到的。
追问2:@Scope("singleton") 的 Bean 是线程安全的吗?
回答:Spring 只保证 Bean 的创建是线程安全的(通过 synchronized 和缓存),并不保证 Bean 内部状态的安全,线程安全需要开发者自己处理。
追问3:如果希望单例 Bean 在一定条件下被销毁并重建,该怎么做?
回答:可以通过 DefaultSingletonBeanRegistry 的 API 移除单例,或利用 @RefreshScope(Spring Cloud)刷新。
Q4:AbstractApplicationContext.refresh() 是如何体现模板方法模式的?
标准回答:refresh() 定义了 12 个固定步骤的算法骨架,其中 obtainFreshBeanFactory()、postProcessBeanFactory()、onRefresh() 等是钩子方法,由子类实现或重写。
追问1:Spring Boot 如何利用这个模板?
回答:Spring Boot 的 ServletWebServerApplicationContext 重写了 onRefresh() 来启动内嵌 Tomcat,正是对钩子方法的典型运用。
追问2:如果我想在容器刷新前做点配置,应该在哪里做?
回答:可以实现 ApplicationContextInitializer,它在 prepareRefresh() 之前被调用,更适合做前置配置。
追问3:模板方法模式和策略模式在 refresh() 中有同时出现吗?
回答:有,例如步骤 5 调用 BeanFactoryPostProcessor 时,容器会根据不同类型执行不同的后处理器,这可以看作策略模式的变体。
Q5:AOP 的实现用到了哪些设计模式?分别对应哪个类?
标准回答:代理模式(JdkDynamicAopProxy / CglibAopProxy)、责任链模式(ReflectiveMethodInvocation)、适配器模式(AdvisorAdapter)、工厂模式(ProxyFactoryBean)。
追问1:JDK 动态代理和 CGLIB 的选择策略是什么?
回答:若目标实现接口则用 JDK 动态代理,否则或强制 proxyTargetClass=true 时使用 CGLIB。
追问2:AOP 拦截器链的终点是什么?
回答:终点是 invokeJoinpoint(),即通过反射直接调用目标对象的方法。
追问3:如何在 AOP 链中实现增强的顺序控制?
回答:通过实现 Ordered 接口或 @Order 注解,Spring 会按顺序将拦截器排序后再执行。
Q6:DispatcherServlet 的 HandlerAdapter 体现了什么模式?为什么这样设计?
标准回答:适配器模式。DispatcherServlet 需要处理多种 Handler,HandlerAdapter 将不同 Handler 统一为 handle(req,res,handler) 调用。
追问1:添加一种新 Handler 需要改动 DispatcherServlet 吗?
回答:不需要,只需新增 HandlerAdapter 实现并注册,完全遵循开闭原则。
追问2:HandlerAdapter 和 HandlerMapping 是如何分工的?
回答:HandlerMapping 负责查找请求对应的 Handler,HandlerAdapter 负责执行 Handler,二者通过职责分离实现高内聚。
追问3:RequestMappingHandlerAdapter 内部还用了哪些模式?
回答:内部使用了策略模式(HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler)来处理参数解析和返回值处理。
Q7:Spring 的事件机制是哪种模式?有哪些核心组件?
标准回答:观察者模式。核心组件:ApplicationEvent、ApplicationListener、ApplicationEventMulticaster(默认 SimpleApplicationEventMulticaster)、ApplicationEventPublisher。
追问1:如何让事件异步执行?
回答:给 SimpleApplicationEventMulticaster 设置 TaskExecutor,或在 @EventListener 方法上添加 @Async。
追问2:@EventListener 是如何被注册的?
回答:EventListenerMethodProcessor(实现 BeanFactoryPostProcessor)扫描 Bean 中的 @EventListener 方法,动态创建适配器并注册到广播器。
追问3:事务性事件监听器如何实现?
回答:使用 @TransactionalEventListener,它会在事务提交后才执行监听器,内部结合了 TransactionSynchronization 机制。
Q8:ConversionService 的转换器选择策略是策略模式吗?为什么?
标准回答:是。它内部维护一组 Converter,convert 方法根据源/目标类型选择第一个匹配的转换器执行,新增转换器只需注册即可。
追问1:如果多个 Converter 都支持同一转换,如何选择?
回答:GenericConversionService 按注册顺序遍历,返回第一个匹配的,注册顺序敏感。
追问2:ConversionService 与 PropertyEditor 的关系?
回答:Spring 同时支持 PropertyEditor(旧式)和 ConversionService,前者基于字符串转换,后者支持泛型类型转换,通常 ConversionService 优先级更高。
追问3:如何自定义全局的日期格式转换?
回答:实现 Converter<String, Date> 并注册到 ConversionService,或者在 @DateTimeFormat 注解中指定格式。
Q9:责任链在 Spring 中有哪些应用?各自有什么特点?
标准回答:AOP 拦截器链(递归调用,可中断/修改)、Servlet Filter 链(顺序执行,可短路)、Spring MVC HandlerInterceptor 链(pre → handler → post)、Spring Security 过滤链(多个 SecurityFilter 按序执行)。
追问1:Filter 和 HandlerInterceptor 链有什么区别?
回答:Filter 工作在 Servlet 容器级别,先于 DispatcherServlet;Interceptor 工作在 Spring MVC 级别,可访问 Handler 和 ModelAndView。
追问2:如何实现自定义的 AOP 拦截器链?
回答:实现 MethodInterceptor 接口,在 invoke() 中调用 invocation.proceed() 传递责任;并可通过 @Aspect 和 @Around 间接实现。
追问3:Spring Security 的过滤链是如何保证顺序的?
回答:通过 SecurityFilterChain 的实现类,结合 @Order 或 Ordered 接口控制多个链的顺序,链内则通过 FilterOrderRegistration 管理。
Q10:Spring 中怎么体现装饰器模式?和代理模式有啥区别?
标准回答:装饰器如 BeanDefinitionDecorator、ContentCachingRequestWrapper,它们在包装对象上增加额外功能但不改变接口;代理模式如 JdkDynamicAopProxy,主要控制访问并可能改变行为。
追问1:BeanDefinitionDecorator 和 BeanDefinitionRegistryPostProcessor 都能修改 Bean 定义,有何不同?
回答:BeanDefinitionDecorator 在 XML 解析时使用,是装饰器;BeanDefinitionRegistryPostProcessor 则是通过编程方式直接修改注册表,影响范围更广。
追问2:装饰器模式在 Spring Boot 的 ServerHttpRequestDecorator 中为什么常用?
回答:在微服务网关中常需要修改请求头或缓存请求体,装饰器允许在不破坏原始请求不可变性的前提下进行功能扩展。
Q11:建造者模式在 Spring 中有哪些体现?相比直接用 new 有什么优势?
标准回答:BeanDefinitionBuilder、RestTemplateBuilder、SpringApplicationBuilder 等。优势:链式调用,可读性强;分离构建过程与表示;可设置默认值,避免构造器过长。
追问1:BeanDefinitionBuilder 和直接 new GenericBeanDefinition 的区别?
回答:BeanDefinitionBuilder 封装了复杂属性设置,如父定义、工厂方法等,避免手动处理 MutablePropertyValues 和 ConstructorArgumentValues 的繁琐。
追问2:什么情况下不推荐使用建造者?
回答:对象简单且属性少时,建造者会增加不必要的代码;或者需要不可变对象时,直接使用构造器或工厂方法更合适。
追问3:Spring 中还有其他建造者模式的应用场景吗?
回答:在 Spring Integration 和 Spring Cloud Stream 等模块中,大量使用建造者来构建消息通道、绑定器等复杂配置。
Q12:多个设计模式在 Spring 中是独立工作的吗?请举例说明它们是如何协作的。
标准回答:不是。例如一次 AOP 代理调用:工厂模式创建原始 Bean,代理模式包装为代理,适配器将通知转为拦截器,责任链依次执行,期间可能还有单例模式的缓存。
追问1:模式协作会不会增加调试难度?
回答:确实会增加理解成本,但一旦掌握每个模式的边界和交接点,调试时反而能快速定位问题所在(例如知道先检查代理再跟责任链)。
追问2:如何避免模式之间的过度耦合?
回答:Spring 大量使用面向接口编程和事件机制来降低模式间的直接依赖,这也是我们在设计类似系统时应遵循的原则。
Q13:模板方法模式和策略模式在 Spring 中可能同时出现吗?如果出现,你会在哪里看到?
标准回答:会同时出现。典型如 AbstractApplicationContext.refresh() 是模板方法,但在 finishBeanFactoryInitialization 中,ConversionService 和 ResourceLoader 使用策略模式;Web 层的 RequestMappinAdapter 内部也结合了模板方法(整体处理框架)和策略(参数解析器/返回值处理器)。
追问1:这两种模式的核心区别是什么?
回答:模板方法用继承改变算法骨架的某些步骤;策略模式用组合替换整个算法。Spring 更偏好组合(策略),但顶层流程更适合模板方法。
追问2:在实际项目中,什么时候该用模板方法而不是策略?
回答:当流程有固定顺序,只是某些步骤会变化时用模板方法;当整个算法家族可以互换时用策略。例如支付流程,固定步骤“验证 → 扣款 → 通知”可以用模板方法,而“扣款”的具体实现可以用策略选择银行卡/支付宝/微信。
Q14:如果你要设计一个框架,你会如何借鉴 Spring 的设计模式应用?
标准回答:首先识别核心不变流程(用模板方法),定义扩展点(钩子 / SPI)。对象创建用工厂和建造者,异构接口用适配器,横切关注点用代理+责任链,状态变化通信用观察者,算法切换用策略。各模式通过接口解耦,最终形成可插拔的生态。
追问1:如何测试这种多模式框架?
回答:依赖注入和接口抽象让单元测试非常容易,可针对各模式的接口进行 Mock,独立验证每个扩展点。
追问2:设计模式用多了会不会影响性能?
回答:会带来一些间接开销(如代理、反射),但架构清晰带来的可维护性和扩展性收益远超微小的性能损失,且 Spring 自身也做了很多优化(如缓存、提前计算)。
Q15(系统设计题):请设计一个轻量级的“插件管理器”,要求能够动态加载/卸载服务,并通过多种方式(注解、配置)注册。说明你将如何使用工厂、单例、策略、观察者等模式来构建它。
标准回答:
- 工厂模式:
PluginFactory接口根据插件定义创建实例,默认实现DefaultPluginFactory读取@Plugin注解反射创建。 - 单例模式:插件管理器本身为容器管理的单例,通过
PluginRegistry缓存已加载的插件实例(容器级单例)。 - 建造者模式:
PluginDefinitionBuilder流式构建插件的元数据(名称、版本、初始化参数等)。 - 策略模式:定义
PluginLifecycleStrategy接口,有立即加载、懒加载、定时加载等不同策略,可动态切换。 - 观察者模式:管理器在注册/卸载插件时发布
PluginRegisteredEvent、PluginUnregisteredEvent,其他组件可监听并更新 UI 或执行资源清理。 - 适配器模式:如果需要兼容不同插件规范(OSGi、Java SPI 等),提供
PluginSpecAdapter统一接口。 - 责任链模式:插件健康检查时,使用
PluginHealthChecker链,每个检查器负责一项(依赖检查、版本兼容等),可中断并报告失败。 - 动态注册:通过扫描指定包下的
@Plugin注解或解析配置文件,借助BeanDefinitionRegistryPostProcessor类似的扩展点在启动时自动注册到PluginRegistry。
追问1:如何保证插件卸载时资源正确释放?
回答:在插件接口中定义 destroy() 方法,由管理器调用;并通过观察者事件通知其他组件释放关联资源。
追问2:如何实现插件间的隔离?
回答:可为每个插件创建独立的 ClassLoader,或在插件工厂中使用子容器(类似 Spring 父子容器)进行类隔离和 Bean 隔离。
追问3:这个设计与 Spring 自身的哪部分最像?
回答:整体思想类似 Spring 的 ApplicationContext 生命周期管理 + BeanFactory 扩展体系。PluginRegistry 类似 DefaultListableBeanFactory,PluginLifecycleStrategy 类似作用域策略,事件通知则直接借鉴了 Spring 的观察者模式实现。
附录:设计模式与 Spring 组件映射速查表
| 设计模式 | 模式意图 | Spring 核心类/接口 | 作用 | 所在模块 |
|---|---|---|---|---|
| 工厂/抽象工厂 | 将实例化逻辑封装,解耦客户端与具体类 | BeanFactory, ApplicationContext, DefaultListableBeanFactory | 创建和管理 Bean,支持多样化的创建方式 | IoC 容器 |
| 单例 | 确保一个类只有一个容器级实例 | DefaultSingletonBeanRegistry(三级缓存) | 保证容器内 Bean 唯一性,解决循环依赖 | IoC 容器 |
| 建造者 | 分离复杂对象的构建与表示 | BeanDefinitionBuilder, RestTemplateBuilder | 简化复杂对象(Bean 定义)的构建 | IoC 容器 / Boot |
| 模板方法 | 定义算法骨架,步骤延迟到子类 | AbstractApplicationContext.refresh(), AbstractPlatformTransactionManager | 固定容器启动流程,留出钩子扩展 | IoC 容器 / 事务 |
| 代理 | 控制对对象的访问,可添加额外行为 | JdkDynamicAopProxy, CglibAopProxy | 动态织入横切关注点(事务、日志) | AOP |
| 适配器 | 统一不兼容的接口 | HandlerAdapter, AdvisorAdapter | 统一不同 Handler 和通知的调用接口 | Web MVC / AOP |
| 装饰器 | 动态为对象添加附加职责 | BeanDefinitionDecorator, ContentCachingRequestWrapper | 增强元数据或请求/响应的功能 | XML 解析 / Web |
| 观察者 | 对象间的一对多依赖,状态变化通知 | ApplicationEvent, ApplicationListener, SimpleApplicationEventMulticaster | 实现事件驱动编程,解耦状态变化与行为 | 事件机制 |
| 策略 | 定义一系列可互换的算法 | ConversionService, Converter, ResourceLoader, ViewResolver | 动态选择转换策略或资源加载策略 | 数据转换 / 资源 |
| 责任链 | 请求沿链传递,多个对象都有处理机会 | ReflectiveMethodInvocation, FilterChain, HandlerExecutionChain | 按序执行拦截器,允许中断或修改 | AOP / Web |
| 解释器 | 解析并执行特定语言的语句 | SpelExpressionParser, Expression | 执行动态表达式,支持配置化逻辑 | SpEL 表达式 |
延伸阅读:
- Rod Johnson. Expert One-on-One J2EE Development without EJB. Wrox, 2004.
(Spring 设计哲学的源头,通篇融入设计模式) - Erich Gamma 等. 设计模式:可复用面向对象软件的基础. 机械工业出版社, 2000.
(GoF 经典,所有模式的原始定义与动机) - 王福强. Spring 揭秘. 人民邮电出版社, 2009.
(深度剖析 Spring 内部机制与模式应用,适合源码级理解) - Craig Walls. Spring in Action, Fifth Edition. Manning, 2019.
(Spring 5 实战,以设计模式视角讲解核心特性) - Eric Freeman 等. Head First 设计模式. O'Reilly, 2004.
(通俗易懂的模式入门,帮助快速建立直观认知) - Spring Framework 官方参考文档(5.3.x):docs.spring.io/spring-fram…
(源码级的权威参考,可追踪每个类与接口的设计意图)