ARTICLE · 1097092
设计模式面试题:从单例到Spring源码,万字图解+实战坑点,看完直接吊打面试官!
📌 摘要|建议先收藏,再精读
面试官连环炮:
“单例有几种写法?DCL 为什么必须加 volatile?”
“JDK 动态代理和 CGLIB 到底啥区别?Spring AOP 默认用哪个?”
“Spring 里用了哪些设计模式?别只背概念,结合源码说说。”
别慌。这篇设计模式面试题万字长文,不堆八股,直接上硬货,从“会背”打到“会写、会讲、会结合 Spring 源码吹”。
本文亮点速览👇
✅ 创建型:单例 4 种实现对比 + DCL 指令重排坑 + 枚举防反射/反序列化;工厂三兄弟演进;建造者链式调用与适用场景。
✅ 结构型:静态代理、JDK 动态代理、CGLIB 一表讲透;适配器模式与 Spring MVC HandlerAdapter;装饰器模式与 Java IO、MyBatis Cache;代理/适配器/装饰器不再傻傻分不清。
✅ 行为型:策略、模板方法、观察者、责任链,UML + 代码 + 实战场景 + 高频坑点,附四模式对比表。
✅ Spring 源码实战:BeanFactory、单例注册表、AOP 代理、ApplicationEvent、JdbcTemplate、Resource、HandlerAdapter、Filter/Interceptor 链……一张图看懂 Spring 如何把设计模式玩出花。
✅ 面试加分:高频坑点、易混对比、协同流程、话术总结,帮你从“背题人”升级成“源码级理解者”。
适合人群:春招秋招、跳槽面试、CRUD 想突破、被设计模式折磨的 Java 人。
读完你至少能:手写 DCL 单例、说清 JDK/CGLIB 差异、答出 Spring 用了哪些模式、避开单例破坏与指令重排坑。
如果只能收藏一篇设计模式面试题,就这篇。
点赞 + 收藏,面试不慌;转发给正在面试的兄弟,一起反向吊打面试官。
关注我,后续继续拆 Spring、JVM、并发高频题。🚀
创建型:单例模式(懒汉/饿汉/双重校验/静态内部类)、工厂模式、建造者模式
创建型设计模式的核心是 "封装对象创建过程,将对象创建与使用分离",解决的是 "如何优雅地 new 对象" 的问题。下面我逐一为您讲解这几个高频考点 👇
单例模式 👑
核心思想:保证一个类在整个应用中只有一个实例,并提供一个全局访问点。
1. 四种经典实现对比表
| 实现方式 | 线程安全 | 懒加载 | 性能 | 推荐指数 | 核心问题 |
|---|---|---|---|---|---|
1.1 饿汉式 🍚 “不管饿不饿,先做饭”
类加载的时候就 new 好实例,天生线程安全,但可能浪费内存。
publicclassHungrySingleton{
privatestaticfinal HungrySingleton INSTANCE = new HungrySingleton();
privateHungrySingleton(){}
publicstatic HungrySingleton getInstance(){
return INSTANCE;
}
}
✅ 简单粗暴 ❌ 类一加载就占资源,如果一直没用就亏了
2. 关键实现代码与坑点
🔹 双重校验锁 (DCL) - 面试必写
publicclassSingleton{
// 必须加volatile!防止指令重排导致空指针
privatestaticvolatile Singleton instance;
privateSingleton(){} // 私有构造器,防止外部new
publicstatic Singleton getInstance(){
if (instance == null) { // 第一次校验:避免不必要的加锁
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次校验:防止多线程同时进入第一个if
instance = new Singleton();
}
}
}
return instance;
}
}
💡 面试官常问坑点:为什么必须加volatile?
new Singleton()不是原子操作,分为三步:分配内存→初始化对象→引用指向内存不加 volatile 可能发生指令重排:分配内存→引用指向内存→初始化对象 此时其他线程拿到的是一个未初始化的空对象,导致 NPE
🔹 静态内部类 - 最优实现
publicclassSingleton{
privateSingleton(){}
// 只有调用getInstance()时,才会加载内部类
privatestaticclassSingletonHolder{
privatestaticfinal Singleton INSTANCE = new Singleton();
}
publicstatic Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
✅ 优点:JVM 在加载类时是单线程的,天然保证线程安全;同时实现了懒加载,性能最优。
枚举单例 🛡️ “反射、反序列化都攻不破”
publicenum EnumSingleton {
INSTANCE;
publicvoiddoSomething(){}
}
✨ 有效防止反射创建新实例、反序列化重创对象,最安全的单例写法。
3. 单例模式的破坏与防御
反射破坏:通过 Class.getDeclaredConstructor().newInstance()可以调用私有构造器序列化破坏:序列化再反序列化会创建新对象 防御手段:在构造器中抛出异常防止反射;实现 readResolve()方法返回单例对象
工厂模式 🏭
核心思想:定义一个创建对象的接口,让子类决定实例化哪个类,将对象创建延迟到子类。
1. 三种工厂模式演进
┌── 简单工厂(一个工厂生产所有产品) 🔧 参数判断
工厂模式 ──┼── 工厂方法(一种产品一个工厂) 🏭 每个工厂只干一件事
└── 抽象工厂(一族产品的超级工厂)🏗️ 创建相关产品家族
2. 适用场景与优缺点
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
2.1 简单工厂(Static Factory)
不是 23 种设计模式之一,但常用。
用一个工厂类根据参数 new 不同子类,违反开闭原则,加新产品得改工厂代码。
publicclassSimpleFactory{
publicstatic Product create(String type){
if ("A".equals(type)) returnnew ProductA();
elsereturnnew ProductB();
}
}
2.2 工厂方法(Factory Method)
定义一个创建对象的接口,让子类决定实例化哪个类。一个产品类对应一个工厂类。
interfaceFactory{ Product create(); }
classFactoryAimplementsFactory{
public Product create(){ returnnew ProductA(); }
}
✅ 符合开闭原则,加新产品只需加新工厂类。
2.3 抽象工厂(Abstract Factory)
创建 一系列 相关或依赖的对象,比如不同操作系统的 UI 组件工厂(按钮、文本框一起搞)。
interfaceUIFactory{
Button createButton();
TextField createTextField();
}
classWindowsFactoryimplementsUIFactory{ ... }
classMacFactoryimplementsUIFactory{ ... }
3. 代码示例(工厂方法)
// 产品接口
publicinterfacePhone{
voidmake();
}
// 具体产品
publicclassIPhoneimplementsPhone{
@Override
publicvoidmake(){
System.out.println("生产苹果手机");
}
}
publicclassXiaomiimplementsPhone{
@Override
publicvoidmake(){
System.out.println("生产小米手机");
}
}
// 工厂接口
publicinterfacePhoneFactory{
Phone createPhone();
}
// 具体工厂
publicclassIPhoneFactoryimplementsPhoneFactory{
@Override
public Phone createPhone(){
returnnew IPhone();
}
}
publicclassXiaomiFactoryimplementsPhoneFactory{
@Override
public Phone createPhone(){
returnnew Xiaomi();
}
}
建造者模式 🔨
核心思想:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
典型:StringBuilder、Lombok 的 @Builder。
一句话:工厂关心“产哪个品牌的车”,建造者关心“这辆车的配置怎么搭”(cpu、内存、硬盘全自定义)。🚗
1. 适用场景
对象构造参数非常多(超过 4 个),且很多参数是可选的 需要创建不可变对象(没有 setter 方法) 构建过程需要分步骤完成,不同步骤顺序可以产生不同结果
2. 经典实现(链式调用版)
publicclassUser{
// 必选参数
privatefinal String username;
privatefinal String password;
// 可选参数
privatefinalint age;
privatefinal String phone;
privatefinal String address;
// 私有构造器,只能通过Builder构建
privateUser(UserBuilder builder){
this.username = builder.username;
this.password = builder.password;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
// 静态内部Builder类
publicstaticclassUserBuilder{
// 必选参数
privatefinal String username;
privatefinal String password;
// 可选参数 - 初始化默认值
privateint age = 0;
private String phone = "";
private String address = "";
// 必选参数通过构造器传入
publicUserBuilder(String username, String password){
this.username = username;
this.password = password;
}
// 可选参数通过链式方法设置
public UserBuilder age(int age){
this.age = age;
returnthis;
}
public UserBuilder phone(String phone){
this.phone = phone;
returnthis;
}
public UserBuilder address(String address){
this.address = address;
returnthis;
}
// 构建最终对象
public User build(){
returnnew User(this);
}
}
}
// 使用方式
publicclassMain{
publicstaticvoidmain(String[] args){
User user = new User.UserBuilder("zhangsan", "123456")
.age(20)
.phone("13800138000")
.address("北京市朝阳区")
.build();
}
}
3. 建造者模式 vs 构造器 vs setter
| 方式 | 优点 | 缺点 |
|---|---|---|
面试总结 📝
单例模式:重点掌握双重校验锁和静态内部类的实现,以及 volatile 关键字的作用和单例破坏问题 工厂模式:理解三种工厂模式的演进过程和各自的适用场景,开闭原则是核心 建造者模式:记住链式调用的实现方式,当构造参数超过 4 个且有很多可选参数时优先使用
结构型:代理模式(JDK 动态代理 vs CGLIB)、适配器模式、装饰器模式
面试官您好!结构型设计模式的核心是 **"如何将类或对象组合成更大的结构,同时保持结构的灵活性和高效性"**。下面我从面试最常考的三个模式逐一为您解答。
代理模式 🔒
1. 核心定义
为其他对象提供一个代理对象,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在不修改目标对象的前提下,增加额外功能(如日志、事务、权限控制)。
2. 核心角色
抽象主题(Subject):定义真实主题和代理主题的共同接口 真实主题(RealSubject):被代理的对象,执行业务逻辑 代理(Proxy):持有真实主题的引用,实现与真实主题相同的接口,在调用前后增加额外操作
3. 代理模式类图

3.1 静态代理
代理类在编译期就存在,需要手动编写。
缺点很明显:一个接口就得写一个代理类,类爆炸。
// 接口
publicinterfaceUserService{
voidsave();
}
// 目标对象
publicclassUserServiceImplimplementsUserService{
publicvoidsave(){ System.out.println("保存用户"); }
}
// 代理类
publicclassUserServiceProxyimplementsUserService{
private UserService target;
publicUserServiceProxy(UserService target){ this.target = target; }
publicvoidsave(){
System.out.println("开启事务");
target.save();
System.out.println("提交事务");
}
}
3.2 JDK 动态代理
利用 java.lang.reflect.Proxy 和 InvocationHandler 在运行时生成代理类。
前提条件:目标对象必须实现接口,代理类会实现相同的接口。
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxyObj, method, args) -> {
System.out.println("开启事务");
Object result = method.invoke(target, args);
System.out.println("提交事务");
return result;
});
proxy.save();
Proxy.newProxyInstance 会动态生成一个实现了指定接口的类($Proxy0),其内部通过反射调用 InvocationHandler.invoke 方法。
局限:只能代理接口,不能代理没有接口的类。
3.3 CGLIB 动态代理
CGLIB(Code Generation Library)基于 ASM 字节码技术,通过生成目标类的子类来实现代理。
因此目标类不能是 final 的,代理方法也不能是 final。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class); // 直接设置父类
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("开启事务");
Object result = proxy.invokeSuper(obj, args);
System.out.println("提交事务");
return result;
});
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.save();
CGLIB 会生成一个 UserServiceImpl$$EnhancerByCGLIB$$xxxx 的子类,重写非 final 方法,在方法内部调用拦截器。
4. 面试核心:JDK 动态代理 vs CGLIB 动态代理 ✨
这是 Spring AOP 的底层实现,也是面试必考点!
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
Proxy.newProxyInstance() | Enhancer.create() | |
InvocationHandler接口的invoke()方法 | MethodInterceptor接口的intercept()方法 |
🔹 Spring AOP 内部实现了这两种方式的自动切换:如果目标对象实现了接口,则采用 JDK 动态代理;否则使用 CGLIB。也可以通过 proxy-target-class=true 强制使用 CGLIB。
5. 适用场景
远程代理:如 RMI、Dubbo 的服务调用 虚拟代理:如图片懒加载 保护代理:如权限控制 智能引用代理:如统计方法调用次数、日志记录 Spring AOP 的核心实现
适配器模式 🔌
1. 核心定义
将一个类的接口转换成客户端期望的另一个接口,使得原本因接口不兼容而无法一起工作的类能够协同工作。
好比去欧洲旅行,插座是圆孔的,你带了个国标扁头充电器,就得用一个转接头。
2. 核心角色
目标接口(Target):客户端期望的接口 适配者(Adaptee):需要被适配的现有接口 适配器(Adapter):实现目标接口,持有适配者的引用,将目标接口的调用转换为适配者接口的调用
3. 适配器模式类图(对象适配器,最常用)

// 目标接口(客户期望的)
interfaceMediaPlayer{
voidplay(String audioType, String fileName);
}
// 被适配者(高级播放器)
classAdvancedMediaPlayer{
voidplayMp4(String fileName){ System.out.println("播放 mp4: " + fileName); }
}
// 适配器
classMediaAdapterimplementsMediaPlayer{
private AdvancedMediaPlayer advancedPlayer;
publicMediaAdapter(){ this.advancedPlayer = new AdvancedMediaPlayer(); }
@Override
publicvoidplay(String audioType, String fileName){
if ("mp4".equalsIgnoreCase(audioType)) {
advancedPlayer.playMp4(fileName); // 转接
}
}
}
4. 两种适配器对比
对象适配器:通过组合适配者对象实现,灵活度高,推荐使用 类适配器:通过继承适配者类实现,Java 不支持多继承,限制较多
5. 适用场景
集成第三方库,接口不匹配时 统一多个不同接口的调用方式 兼容旧系统的接口 典型例子:Java 的 InputStreamReader和OutputStreamWriter
5.1 🌰 经典应用:Spring MVC 的 HandlerAdapter
Spring 中有多种处理器(Controller、HttpRequestHandler、Servlet 等),DispatcherServlet 不能直接调用它们,于是用了适配器模式。
publicinterfaceHandlerAdapter{
booleansupports(Object handler);
ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object handler);
}
// 比如适配 Controller 接口的适配器
publicclassSimpleControllerHandlerAdapterimplementsHandlerAdapter{
publicbooleansupports(Object handler){ return handler instanceof Controller; }
public ModelAndView handle(...){ return ((Controller)handler).handleRequest(req, res); }
}
无论处理器是什么类型,通过适配器统一了调用方式 ✨。
装饰器模式 🎨
1. 核心定义
动态地给一个对象添加额外的职责,就增加功能来说,装饰器模式比生成子类更加灵活。它遵循 "组合优于继承" 的原则。
像你玩 RPG 游戏,角色可以不断穿戴装备:基础角色→拿剑→穿铠甲→带戒指,每一层装饰都增强功能。
2. 核心角色
抽象组件(Component):定义被装饰对象和装饰器的共同接口 具体组件(ConcreteComponent):被装饰的原始对象 抽象装饰器(Decorator):继承抽象组件,持有抽象组件的引用 具体装饰器(ConcreteDecorator):实现具体的装饰功能
// 抽象构件
interfaceCoffee{
doublecost();
String desc();
}
// 具体构件
classSimpleCoffeeimplementsCoffee{
publicdoublecost(){ return10; }
public String desc(){ return"咖啡"; }
}
// 抽象装饰器
abstractclassCoffeeDecoratorimplementsCoffee{
protected Coffee coffee;
publicCoffeeDecorator(Coffee coffee){ this.coffee = coffee; }
}
// 具体装饰器:加奶
classMilkDecoratorextendsCoffeeDecorator{
publicMilkDecorator(Coffee coffee){ super(coffee); }
publicdoublecost(){ return coffee.cost() + 2; }
public String desc(){ return coffee.desc() + "+牛奶"; }
}
// 使用
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
System.out.println(coffee.desc() + " 价格:" + coffee.cost()); // 咖啡+牛奶 价格:12
3. 装饰器模式类图

4. 4. 适用场景
需要动态地给对象添加功能,且这些功能可以动态撤销 需要为大量兄弟类添加功能,避免类爆炸 典型例子:Java IO 流体系( BufferedReader装饰FileReader)
易混淆模式对比 🧐
这是面试官最爱问的区分题!
| 模式 | 核心目的 | 关系 | 典型应用 |
|---|---|---|---|

面试加分回答 💯
代理模式:Spring AOP 中,如果目标类实现了接口,默认使用 JDK 动态代理;如果没有实现接口,则使用 CGLIB。可以通过 proxy-target-class=true强制使用 CGLIB。适配器模式:Spring MVC 中的 HandlerAdapter就是适配器模式的典型应用,它将不同类型的处理器(如Controller、HttpRequestHandler)适配成统一的处理器接口。装饰器模式:MyBatis 中的 Cache接口及其实现类使用了装饰器模式,通过不同的装饰器为缓存添加了序列化、日志、定时清理等功能。
行为型:策略模式、模板方法模式、观察者模式、责任链模式
面试官您好!接下来我会从核心思想、角色结构、适用场景、代码实现、优缺点、实际应用这几个维度,为您逐一讲解这四个高频行为型设计模式。
策略模式 (Strategy Pattern) 🎨
核心思想
定义一系列算法,将每个算法封装成独立的类,并使它们可以互相替换。让算法的变化独立于使用算法的客户端。
UML 类图

💡 重点:上下文持有策略接口引用,具体算法实现完全解耦。
核心角色
**Context (上下文)**:持有策略对象的引用,负责调用策略 **Strategy (抽象策略)**:定义所有算法的公共接口 **ConcreteStrategy (具体策略)**:实现抽象策略接口,提供具体算法
适用场景 ✅
一个类有多种行为,且需要在运行时动态切换 算法需要独立于客户端变化 避免使用多重条件判断 (if-else/switch)
Java 关键实现
// 抽象策略
publicinterfacePaymentStrategy{
voidpay(double amount);
}
// 具体策略
publicclassAlipayStrategyimplementsPaymentStrategy{
@Override
publicvoidpay(double amount){
System.out.println("支付宝支付:" + amount + "元");
}
}
// 上下文
publicclassOrder{
private PaymentStrategy paymentStrategy;
// 运行时动态设置策略
publicvoidsetPaymentStrategy(PaymentStrategy paymentStrategy){
this.paymentStrategy = paymentStrategy;
}
publicvoidcheckout(double amount){
paymentStrategy.pay(amount);
}
}
实际应用
JDK 的 Comparator接口 (排序策略)Spring 的 Resource接口 (资源访问策略)电商支付方式 (支付宝 / 微信 / 银行卡)
优缺点
| 优点 | 缺点 |
|---|---|
面试高频坑点 ⚠️
策略模式和简单工厂模式的区别:工厂是创建对象,策略是封装行为 策略模式和状态模式的区别:状态模式的状态之间有依赖关系,策略模式的策略之间完全独立
模板方法模式 (Template Method Pattern) 📋
核心思想
定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。让子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
UML 类图

💡 重点:基类用final保护骨架,对外暴露钩子方法brew()/addCondiments()。
核心角色
**AbstractClass (抽象类)**:定义模板方法和算法骨架 **ConcreteClass (具体子类)**:实现抽象类中的抽象步骤
适用场景 ✅
多个子类有公共的方法,且逻辑基本相同 算法的整体结构固定,但个别步骤需要变化 需要控制子类的扩展
Java 关键实现
// 抽象类
publicabstractclassCoffeeMaker{
// 模板方法,final防止子类重写整个算法
publicfinalvoidmakeCoffee(){
boilWater();
brewCoffee();
pourInCup();
addCondiments(); // 钩子方法
}
// 公共步骤
privatevoidboilWater(){
System.out.println("烧开水");
}
privatevoidpourInCup(){
System.out.println("倒入杯中");
}
// 抽象步骤,由子类实现
protectedabstractvoidbrewCoffee();
// 钩子方法,子类可选实现
protectedvoidaddCondiments(){}
}
// 具体子类
publicclassAmericanoMakerextendsCoffeeMaker{
@Override
protectedvoidbrewCoffee(){
System.out.println("冲泡美式咖啡");
}
}
实际应用
JDK 的 AbstractList、AbstractMap等抽象类Spring 的 JdbcTemplate、HibernateTemplateServlet 的 HttpServlet(doGet/doPost 方法)
优缺点
| 优点 | 缺点 |
|---|---|
面试高频坑点 ⚠️
模板方法一定要用 final修饰,防止子类重写整个算法钩子方法 (Hook Method) 是模板方法的重要扩展点 模板方法模式是继承的典型应用,而策略模式是组合的典型应用
观察者模式 (Observer Pattern) 👀
核心思想
定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。
UML 类图

💡 重点:主题持有观察者集合,状态变化时遍历通知,支持动态增删观察者。
核心角色
Subject (主题):被观察的对象,维护观察者列表 Observer (观察者):定义更新接口 ConcreteSubject (具体主题):实现主题接口,状态变化时通知观察者 ConcreteObserver (具体观察者):实现观察者接口,处理更新
适用场景 ✅
一个对象的变化需要通知其他对象 事件驱动系统 消息订阅 - 发布系统
Java 关键实现
// 主题接口
publicinterfaceSubject{
voidregisterObserver(Observer observer);
voidremoveObserver(Observer observer);
voidnotifyObservers();
}
// 观察者接口
publicinterfaceObserver{
voidupdate(String message);
}
// 具体主题
publicclassNewsAgencyimplementsSubject{
private List<Observer> observers = new ArrayList<>();
private String news;
@Override
publicvoidregisterObserver(Observer observer){
observers.add(observer);
}
@Override
publicvoidremoveObserver(Observer observer){
observers.remove(observer);
}
@Override
publicvoidnotifyObservers(){
for (Observer observer : observers) {
observer.update(news);
}
}
publicvoidsetNews(String news){
this.news = news;
notifyObservers();
}
}
实际应用
JDK 的 java.util.Observer和java.util.Observable(已过时)Spring 的事件机制 ( ApplicationEvent和ApplicationListener)GUI 编程中的事件监听器 消息队列的发布 - 订阅模式
优缺点
| 优点 | 缺点 |
|---|---|
面试高频坑点 ⚠️
JDK 自带的 Observable是类而不是接口,有单继承限制Spring 事件机制是观察者模式的典型应用,支持异步事件 观察者模式和发布 - 订阅模式的区别:发布 - 订阅模式多了一个消息中间件,解耦更彻底
责任链模式 (Chain of Responsibility Pattern) 🔗
核心思想
将请求的发送者和接收者解耦,让多个对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
UML 类图

💡 重点:每个处理器持有下一个处理器引用,可灵活增加、调序或断开链。
核心角色
Handler (抽象处理者):定义处理请求的接口,持有下一个处理者的引用 ConcreteHandler (具体处理者):实现处理请求的方法,决定是否处理请求,否则传递给下一个处理者
适用场景 ✅
多个对象可以处理同一个请求,且处理者不确定 请求的处理需要按顺序执行 需要动态添加或删除处理者
Java 关键实现
// 抽象处理者
publicabstractclassLeaveHandler{
protected LeaveHandler nextHandler;
publicvoidsetNextHandler(LeaveHandler nextHandler){
this.nextHandler = nextHandler;
}
publicabstractvoidhandleRequest(int days);
}
// 具体处理者
publicclassTeamLeaderextendsLeaveHandler{
@Override
publicvoidhandleRequest(int days){
if (days <= 3) {
System.out.println("组长批准请假" + days + "天");
} elseif (nextHandler != null) {
nextHandler.handleRequest(days);
}
}
}
publicclassManagerextendsLeaveHandler{
@Override
publicvoidhandleRequest(int days){
if (days <= 7) {
System.out.println("经理批准请假" + days + "天");
} elseif (nextHandler != null) {
nextHandler.handleRequest(days);
}
}
}
实际应用
Servlet 的 Filter链Spring MVC 的 HandlerInterceptor拦截器链Netty 的 ChannelPipeline和ChannelHandler日志框架的级别处理 (DEBUG/INFO/WARN/ERROR)
优缺点
| 优点 | 缺点 |
|---|---|
面试高频坑点 ⚠️
责任链可以是纯责任链 (必须有一个处理者处理) 或不纯责任链 (可以都不处理) Spring 的拦截器链是责任链模式的典型应用 责任链模式和装饰器模式的区别:装饰器模式是增强对象功能,责任链模式是处理请求
四个模式对比总结 📊
| 模式 | 核心思想 | 解决的问题 | 关键技术 | 典型应用 |
|---|---|---|---|---|
🚀 结合实战,多说一句加分项
策略+责任链可以组合:营销规则引擎里,先由责任链初步过滤,匹配的再调用具体策略计算优惠。 观察者常配合异步消息,进一步解耦(如在 @EventListener里发MQ)。模板方法中结合钩子方法,让子类控制流程的可选部分。
Spring 中用到了哪些设计模式?
面试官您好😊!Spring 框架之所以能成为 Java 生态的绝对霸主,核心原因之一就是它把经典设计模式运用到了极致,实现了低耦合、高扩展、可插拔的架构特性。我梳理了面试中最常考、Spring 最核心的 7 种设计模式,结合具体源码场景给您说明。
🧩 一图胜千言:Spring 核心模块中的设计模式分布

核心设计模式总览表
| 设计模式 | 核心作用 | Spring 典型应用场景 | 表情包 |
|---|---|---|---|
高频考点深度解析(面试必答)
1. 🏭 工厂模式(Spring 的基石)
简单工厂:BeanFactory 的 getBean()方法,根据 Bean 名称 / 类型创建对象,是最简单的工厂实现工厂方法:每个复杂对象对应一个 FactoryBean,比如 SqlSessionFactoryBean,把对象创建逻辑与业务代码完全隔离抽象工厂:ApplicationContext,同时管理 Bean 的创建、生命周期、依赖注入等全流程 面试加分:BeanFactory 是 Spring 的通用 Bean 工厂;FactoryBean 是特殊 Bean,专门用来创建特定类型的复杂对象
2. 🔒 单例模式(Spring 默认 Bean 作用域)
实现方式:采用单例注册表,用 ConcurrentHashMap存储单例 Bean 实例核心源码: DefaultSingletonBeanRegistry中的singletonObjects一级缓存设计优势:避免频繁创建销毁对象的性能开销,统一管理 Bean 的生命周期
// 容器就是一个大工厂,返回给我们单例 Bean
ApplicationContext ctx = ...;
UserService userService = ctx.getBean(UserService.class); // 每次拿到的都是同一个对象
3. 🎭 代理模式(AOP 的灵魂)
核心原理:运行时生成代理对象,在目标方法执行前后插入增强逻辑 两种实现: JDK 动态代理:基于接口实现,要求目标类必须实现接口 CGLIB 动态代理:基于继承实现,目标类可以不实现接口 Spring 默认策略:目标类实现接口用 JDK 代理,否则用 CGLIB 代理 面试加分:Spring AOP 是运行时动态代理,AspectJ 是编译时 / 类加载时织入
@Service
publicclassOrderService{
@Transactional// 背后是事务代理对象在干活
publicvoidplaceOrder(Order order){ ... }
}
4. 👀 观察者模式(事件驱动模型)
Spring 内置事件驱动模型,用于模块间松耦合通信。
三个核心角色: 事件源:ApplicationContext,负责发布事件 事件:ApplicationEvent,比如 ContextRefreshedEvent、ContextClosedEvent监听器:ApplicationListener,负责处理对应事件 设计优势:完全解耦事件发布者和订阅者,新增监听器无需修改原有代码
@Component
publicclassOrderListener{
@EventListener
publicvoidhandleOrderPlaced(OrderPlacedEvent event){
// 发送短信、记录日志等,完全不侵入订单主流程
}
}
5. 📋 模板方法模式(各种 Template 的底层)
Spring 提供了大量 *Template,它们把固定的流程(获取连接→执行→释放资源)写好,只留出可变部分让你填充。
核心思想:父类封装固定不变的流程,子类实现可变的业务步骤
典型例子:JdbcTemplate 封装了获取连接、执行 SQL、关闭连接的固定流程,用户只需编写 SQL 和处理结果集
设计优势:极大减少重复代码,统一流程规范
JdbcTemplate:执行 SQL 的骨架已定,你只需传入 SQL 和参数。RestTemplate:发起 HTTP 请求的标准流程。RedisTemplate、RabbitTemplate同理。
jdbcTemplate.query("SELECT * FROM user WHERE id = ?",
new Object[]{1},
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")));
// 我不需要关心连接、Statement、ResultSet 的关闭 —— 模板方法全包了
6. 🎯 策略模式(灵活切换实现)
核心思想:定义一系列算法,每个算法独立封装,可互相替换 典型例子:Resource 接口有 ClassPathResource、FileSystemResource、UrlResource等实现,Spring 根据资源路径自动选择加载策略设计优势:扩展性极强,新增算法无需修改原有逻辑
7. 🎨 装饰器模式(动态增强对象)
核心思想:通过包装对象动态添加功能,比继承更灵活 典型例子:BeanWrapperImpl 包装 Bean 对象,提供属性访问、类型转换等功能; HttpServletRequestWrapper可动态增强请求对象
8. 🔌 适配器模式(Spring MVC 的灵魂)
DispatcherServlet 拿到 Handler 之后,并不知道是普通的 @Controller、还是 HttpRequestHandler 或 Servlet。
于是它通过 HandlerAdapter 来适配:
RequestMappingHandlerAdapter→ 适配@RequestMapping方法HttpRequestHandlerAdapter→ 适配HttpRequestHandlerSimpleServletHandlerAdapter→ 适配原生Servlet
各种 Handler 就像不同形状的插头,HandlerAdapter 就是万能转接头🔌。
9. ⛓️ 责任链模式(请求过滤与拦截)
Servlet Filter 链: Filter一个个传递request/response。Spring Interceptor 链: preHandle中任一返回false即可中断链条。AOP 通知链:多个切面按照优先级构成一条调用链,逐个执行。
// 拦截器链:日志拦截器 -> 权限拦截器 -> 真正 Controller
publicclassLogInterceptorimplementsHandlerInterceptor{ ... }
Spring 核心流程设计模式协同图

面试加分项(体现技术深度)
除了以上核心模式,Spring 还大量运用了其他经典设计模式:
适配器模式:HandlerAdapter 适配不同类型的 Controller(@RequestMapping、@Controller) 责任链模式:Spring MVC 拦截器链、Spring Security 过滤器链 迭代器模式:BeanDefinition 遍历、集合类型依赖注入 桥接模式:Spring 事务抽象,分离事务管理与具体实现(JDBC、JPA、Hibernate) 🎀 装饰器: BeanDefinitionDecorator、Session 管理的SessionRepositoryRequestWrapper;不改变接口,动态增强功能🛠️ 建造者: RestTemplateBuilder、ResponseEntity.ok().body(...)的链式构建;一步步构造复杂对象📑 原型: @Scope("prototype");每次获取新实例
结尾总结(给面试官留好印象)
Spring 不是生硬地套模式,而是用模式解决了实际痛点:
工厂 + 单例 → 管好对象的生命周期 代理 + 责任链 → 无侵入地加功能 模板方法 → 让开发者只关注业务逻辑 适配器 → 让框架兼容各种组件 观察者 → 实现组件间松耦合
总的来说,Spring 的设计哲学就是 "面向接口编程,而不是面向实现编程",所有设计模式的运用都是为了实现这个目标。通过合理运用设计模式,Spring 才能做到如此灵活和可扩展,成为 Java 开发的事实标准。