夜雨聆风学习资料网

ARTICLE · 1097092

设计模式面试题:从单例到Spring源码,万字图解+实战坑点,看完直接吊打面试官!

设计模式面试题:从单例到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. 四种经典实现对比表

实现方式线程安全懒加载性能推荐指数核心问题
饿汉式
✅ 是
❌ 否
⭐⭐⭐⭐⭐
⭐⭐⭐
类加载时就初始化,浪费内存
懒汉式 (非同步)
❌ 否
✅ 是
⭐⭐⭐⭐⭐
❌ 不推荐
多线程下会创建多个实例
双重校验锁 (DCL)
✅ 是
✅ 是
⭐⭐⭐⭐
⭐⭐⭐⭐
volatile 关键字防止指令重排
静态内部类
✅ 是
✅ 是
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
利用 JVM 类加载机制保证线程安全

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

方式优点缺点
重叠构造器
简单直接
参数多时代码可读性差,容易传错参数
setter 方法
灵活,可读性好
对象状态可变,无法保证线程安全
建造者模式
可读性好,对象不可变,参数校验方便
代码量稍大,需要额外编写 Builder 类

面试总结 📝

  • 单例模式:重点掌握双重校验锁和静态内部类的实现,以及 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 动态代理
底层实现
基于 Java 反射机制,生成实现了目标接口的匿名类
基于 ASM 字节码框架,生成目标类的子类
要求
目标类必须实现至少一个接口
目标类不能被 final 修饰,方法不能是 final/private
性能
JDK8 + 后性能大幅提升,与 CGLIB 基本持平
生成字节码耗时较长,但方法调用更快
Spring AOP 默认
当目标类实现接口时使用
当目标类没有实现接口时使用
代理对象创建
通过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
适配器模式
转换接口,解决不兼容问题
适配器与适配者接口不同
InputStreamReader
装饰器模式
动态增强对象功能
装饰器与被装饰对象实现相同接口
Java IO 流

面试加分回答 💯

  1. 代理模式:Spring AOP 中,如果目标类实现了接口,默认使用 JDK 动态代理;如果没有实现接口,则使用 CGLIB。可以通过proxy-target-class=true强制使用 CGLIB。
  2. 适配器模式:Spring MVC 中的HandlerAdapter就是适配器模式的典型应用,它将不同类型的处理器(如Controller、HttpRequestHandler)适配成统一的处理器接口。
  3. 装饰器模式:MyBatis 中的Cache接口及其实现类使用了装饰器模式,通过不同的装饰器为缓存添加了序列化、日志、定时清理等功能。

行为型:策略模式、模板方法模式、观察者模式、责任链模式

面试官您好!接下来我会从核心思想、角色结构、适用场景、代码实现、优缺点、实际应用这几个维度,为您逐一讲解这四个高频行为型设计模式。

策略模式 (Strategy Pattern) 🎨

核心思想

定义一系列算法,将每个算法封装成独立的类,并使它们可以互相替换。让算法的变化独立于使用算法的客户端。

UML 类图

💡 重点:上下文持有策略接口引用,具体算法实现完全解耦。

核心角色

  1. **Context (上下文)**:持有策略对象的引用,负责调用策略
  2. **Strategy (抽象策略)**:定义所有算法的公共接口
  3. **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()。

核心角色

  1. **AbstractClass (抽象类)**:定义模板方法和算法骨架
  2. **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、HibernateTemplate
  • Servlet 的HttpServlet(doGet/doPost 方法)

优缺点

优点缺点
代码复用性高
每个不同实现都需要一个子类
算法结构统一
继承关系限制了灵活性
便于维护
父类添加新步骤,所有子类都要修改

面试高频坑点 ⚠️

  • 模板方法一定要用final修饰,防止子类重写整个算法
  • 钩子方法 (Hook Method) 是模板方法的重要扩展点
  • 模板方法模式是继承的典型应用,而策略模式是组合的典型应用

观察者模式 (Observer Pattern) 👀

核心思想

定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。

UML 类图

💡 重点:主题持有观察者集合,状态变化时遍历通知,支持动态增删观察者。

核心角色

  1. Subject (主题):被观察的对象,维护观察者列表
  2. Observer (观察者):定义更新接口
  3. ConcreteSubject (具体主题):实现主题接口,状态变化时通知观察者
  4. 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 类图

💡 重点:每个处理器持有下一个处理器引用,可灵活增加、调序或断开链。

核心角色

  1. Handler (抽象处理者):定义处理请求的接口,持有下一个处理者的引用
  2. 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 的拦截器链是责任链模式的典型应用
  • 责任链模式和装饰器模式的区别:装饰器模式是增强对象功能,责任链模式是处理请求

四个模式对比总结 📊

模式核心思想解决的问题关键技术典型应用
策略模式
封装算法,可互换
多重条件判断
组合
支付方式、排序算法
模板方法
定义骨架,延迟实现
代码重复
继承
JdbcTemplate、HttpServlet
观察者模式
一对多通知
状态变化通知
注册 - 通知
Spring 事件、消息订阅
责任链模式
链式处理请求
请求处理者不确定
链式引用
Filter、拦截器

🚀 结合实战,多说一句加分项

  • 策略+责任链可以组合:营销规则引擎里,先由责任链初步过滤,匹配的再调用具体策略计算优惠。
  • 观察者常配合异步消息,进一步解耦(如在@EventListener里发MQ)。
  • 模板方法中结合钩子方法,让子类控制流程的可选部分。

Spring 中用到了哪些设计模式?

面试官您好😊!Spring 框架之所以能成为 Java 生态的绝对霸主,核心原因之一就是它把经典设计模式运用到了极致,实现了低耦合、高扩展、可插拔的架构特性。我梳理了面试中最常考、Spring 最核心的 7 种设计模式,结合具体源码场景给您说明。

🧩 一图胜千言:Spring 核心模块中的设计模式分布

核心设计模式总览表

设计模式核心作用Spring 典型应用场景表情包
工厂模式(简单 / 工厂方法 / 抽象工厂)
统一对象创建,隔离业务逻辑与实例化过程
BeanFactory、ApplicationContext、FactoryBean
🏭
单例模式
保证容器中一个 Bean 只有一个实例
默认单例 Bean、单例注册表
🔒
代理模式
不修改原代码实现功能增强
AOP 动态代理(JDK/CGLIB)
🎭
观察者模式
实现事件驱动,解耦事件发布与订阅
Spring 事件机制(ApplicationEvent/Listener)
👀
模板方法模式
封装不变流程,扩展可变部分
JdbcTemplate、RedisTemplate、TransactionTemplate
📋
策略模式
同一接口多种实现,动态切换
Resource 资源加载、InstantiationStrategy 实例化策略
🎯
装饰器模式
动态给对象添加额外功能
BeanWrapperImpl、各种 Wrapper 包装类
🎨

高频考点深度解析(面试必答)

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 → 适配 HttpRequestHandler
  • SimpleServletHandlerAdapter → 适配原生 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 开发的事实标准。

相关学习资料