🎨 Java设计模式面试10题(2026版)

单例·工厂·策略·代理·Spring设计模式 | 每道题含答案+追问+代码 | 实习生面试通过率提升50%

Q1:手写一个线程安全的懒汉式单例模式

高频必手写

面试官考察:DCL(Double-Check Locking)、volatile、构造私有化、序列化安全。

public class Singleton {
    // volatile:禁止指令重排,防止半初始化对象
    private static volatile Singleton instance;
    
    private Singleton() {
        // 防反射攻击
        if (instance != null) throw new RuntimeException("已存在实例");
    }
    
    public static Singleton getInstance() {
        if (instance == null) {                  // 第一重检查:减少锁竞争
            synchronized (Singleton.class) {
                if (instance == null) {          // 第二重检查:确保只创建一次
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    // 防序列化破坏单例
    protected Object readResolve() { return getInstance(); }
}

追问:为什么用volatile?

new Singleton() 分三步:分配内存→初始化对象→赋值给instance。JIT可能重排后两步,导致其他线程读到未初始化的对象。volatile禁止这个重排序。

追问:枚举单例和DCL哪个更好?

枚举单例更简单安全(天生防序列化、防反射),但不能懒加载也不能继承。DCL灵活但代码略复杂。Josh Bloch推荐枚举。

Q2:简单工厂、工厂方法、抽象工厂区别

必问

维度简单工厂工厂方法抽象工厂
复杂度
创建对象数单个产品单个产品族多个产品族
扩展性差(改工厂类)好(加新工厂)好(加新工厂族)
开闭原则违反遵循遵循
Spring对标BeanFactoryFactoryBeanApplicationContext

追问:Spring中哪些地方用了工厂模式?

BeanFactory(简单工厂)、FactoryBean接口(工厂方法)、ApplicationContext(抽象工厂——创建一整套Bean)。

Q3:策略模式解决什么问题?和if-else有何区别?

场景设计

// 策略接口
interface PaymentStrategy {
    void pay(double amount);
}

// 具体策略
class AlipayStrategy implements PaymentStrategy {
    public void pay(double amount) { System.out.println("支付宝付" + amount); }
}
class WechatPayStrategy implements PaymentStrategy {
    public void pay(double amount) { System.out.println("微信付" + amount); }
}

// 上下文——组合策略
class PaymentContext {
    private PaymentStrategy strategy;
    public PaymentContext(PaymentStrategy s) { this.strategy = s; }
    public void execute(double amount) { strategy.pay(amount); }
}

// 使用
PaymentContext ctx = new PaymentContext(new AlipayStrategy());
ctx.execute(100);

追问:策略模式和状态模式怎么区分?

策略模式是客户端主动选择用哪个算法(外部切换),状态模式是对象自身根据状态自动切换行为(内部驱动)。

Q4:观察者模式在什么场景用?

框架应用

一对多依赖关系,一个对象变化时通知所有依赖者。典型场景:事件监听、消息推送、数据变更通知。

追问:Spring事件机制是什么模式?

观察者模式。ApplicationEvent(事件)→ ApplicationListener(监听器/观察者)→ ApplicationContext(发布者/被观察者)。@EventListener注解就是观察者模式的Spring实现。

追问:和发布-订阅模式有啥不同?

观察者模式是同进程内同步通知,发布-订阅通过消息中间件(如MQ)异步解耦,跨进程通信。

Q5:JDK动态代理和CGLIB代理的区别

Spring核心

维度JDK动态代理CGLIB代理
实现方式实现接口继承父类
核心类Proxy + InvocationHandlerEnhancer + MethodInterceptor
要求必须有接口类不能是final
性能反射,稍慢字节码生成,更快
Spring默认接口用JDK无接口用CGLIB

追问:Spring Boot 2.0后默认用哪个?

默认CGLIB。配置 spring.aop.proxy-target-class=true(默认就是true)。因为CGLIB不需要接口,更灵活。

Q6:装饰器模式真实场景

IO框架

Java IO流就是装饰器模式的最佳案例:

// BufferedReader 装饰了 FileReader,在不修改原类的情况下增加了缓冲功能
BufferedReader br = new BufferedReader(new FileReader("test.txt"));
// 层层装饰:缓冲→输入流→文件
// 每一层只加一个新功能,不改变原类的接口

追问:装饰器模式和代理模式区别?

装饰器是增强功能(加法),代理是控制访问(控制权)。装饰器可以无限叠,代理通常一对一。但代码结构几乎一样——区别在意图。

Q7:模板方法模式在框架中的应用

框架设计

// AbstractApplicationContext.refresh() 就是模板方法
public void refresh() {
    prepareRefresh();          // ①准备
    obtainFreshBeanFactory();  // ②获取BeanFactory
    prepareBeanFactory();      // ③准备BeanFactory
    postProcessBeanFactory();  // ④后处理(钩子方法)
    invokeBeanFactoryPostProcessors(); // ⑤执行后处理器
    registerBeanPostProcessors();      // ⑥注册Bean后处理器
    // ...更多步骤
    finishRefresh();           // ⑦完成刷新
    // 子类通过重写 postProcessBeanFactory() 来定制行为
}

追问:钩子方法和抽象方法的区别?

钩子方法有默认空实现(子类可选重写),抽象方法必须子类实现。钩子给你"可以选择"的灵活性。

Q8:适配器模式的两个实现方式

兼容改造

类适配器(继承)对象适配器(组合)。Java只支持对象适配器(单继承限制)。

// 对象适配器——推荐
interface Target { void request(); }
class Adaptee { void specificRequest() { System.out.println("原有方法"); } }
class Adapter implements Target {
    private Adaptee adaptee = new Adaptee();
    public void request() { adaptee.specificRequest(); }  // 转换调用
}

追问:Spring MVC的HandlerAdapter是什么模式?

适配器模式。DispatcherServlet不关心具体Handler类型(Controller/HttpRequestHandler),通过HandlerAdapter统一调用。

Q9:责任链模式如何在拦截器中使用

Web应用

Servlet Filter链、Spring Interceptor链都是责任链模式。每个拦截器处理完调用chain.doFilter()传给下一个。

// Spring拦截器链伪代码
class HandlerExecutionChain {
    HandlerInterceptor[] interceptors;
    int index = -1;
    
    boolean applyPreHandle(request, response) {
        for (int i = 0; i < interceptors.length; i++) {
            if (!interceptors[i].preHandle(request, response, handler)) {
                triggerAfterCompletion(request, response);
                return false;  // 被某个拦截器终止
            }
        }
        return true;
    }
}

追问:如何实现一个可动态增删拦截器的责任链?

用链表结构,每个节点持有下一节点的引用。LinkedList天然支持动态增删。核心:每个节点执行完自己的逻辑后调用next.handle()

Q10:Spring中一共用了哪些设计模式?(总结题)

必考总结

模式Spring中的应用
单例Bean默认单例(scope=singleton)
工厂BeanFactory、FactoryBean
代理AOP的JDK/CGLIB代理
模板方法AbstractApplicationContext.refresh()、JdbcTemplate
观察者ApplicationEvent + @EventListener
适配器HandlerAdapter、AdvisorAdapter
策略Resource接口的不同实现(ClassPathResource/FileUrlResource)
装饰器BeanDefinitionDecorator、ServerHttpRequestDecorator
责任链Interceptor链、Filter链
建造者RestTemplateBuilder、MockMvcBuilders

追问:说出一个Spring不是设计模式但类似模式的设计

依赖注入(DI)本身不是GoF23种设计模式之一,但它实现了"好莱坞原则"——Don't call us, we'll call you。本质是控制反转(IoC)的实现。