JavaEE进阶——Spring核心设计模式深度剖析

目录

[Spring 框架核心设计模式深度解析](#Spring 框架核心设计模式深度解析)

一、单例模式 (Singleton Pattern)

[1. 基础形态:饿汉式 vs 懒汉式](#1. 基础形态:饿汉式 vs 懒汉式)

[2. 进阶形态:双重检查锁定 (DCL) 与 静态内部类](#2. 进阶形态:双重检查锁定 (DCL) 与 静态内部类)

[3. Spring 中的终极落地](#3. Spring 中的终极落地)

[3.1 核心实现:单例注册表](#3.1 核心实现:单例注册表)

[3.2 获取单例的逻辑 (getSingleton)](#3.2 获取单例的逻辑 (getSingleton))

[3.3 线程安全与并发](#3.3 线程安全与并发)

二、工厂模式 (Factory Pattern)

[1. 基础形态:简单工厂 (Simple Factory)](#1. 基础形态:简单工厂 (Simple Factory))

[2. 进阶形态:工厂方法模式 (Factory Method)](#2. 进阶形态:工厂方法模式 (Factory Method))

[3. Spring 中的终极落地](#3. Spring 中的终极落地)

[3.1 容器级工厂:BeanFactory](#3.1 容器级工厂:BeanFactory)

[3.2 用户级工厂:FactoryBean 接口(重点)](#3.2 用户级工厂:FactoryBean 接口(重点))

三、代理模式 (Proxy Pattern)

[1. 基础形态:静态代理 (Static Proxy)](#1. 基础形态:静态代理 (Static Proxy))

[2. 进阶形态:JDK 动态代理 (JDK Dynamic Proxy)](#2. 进阶形态:JDK 动态代理 (JDK Dynamic Proxy))

[3. 进阶形态:CGLIB 动态代理](#3. 进阶形态:CGLIB 动态代理)

[4. Spring 中的 AOP 源码实现](#4. Spring 中的 AOP 源码实现)

四、适配器模式 (Adapter Pattern)

[1. 基础形态:对象适配器](#1. 基础形态:对象适配器)

[2. Spring 中的宏大叙事:HandlerAdapter](#2. Spring 中的宏大叙事:HandlerAdapter)

五、策略模式 (Strategy Pattern)

[1. 基础实现](#1. 基础实现)

[2. Spring 中的资源加载 (Resource)](#2. Spring 中的资源加载 (Resource))

[3. Spring 中的 Bean 实例化 (InstantiationStrategy)](#3. Spring 中的 Bean 实例化 (InstantiationStrategy))

六、模板模式 (Template Method Pattern)

[1. 经典继承式模板](#1. 经典继承式模板)

[2. Spring 的变体:模板 + 回调 (Template + Callback)](#2. Spring 的变体:模板 + 回调 (Template + Callback))

七、观察者模式 (Observer Pattern)

[1. 基础实现](#1. 基础实现)

[2. Spring 的事件驱动模型 (Event-Driven)](#2. Spring 的事件驱动模型 (Event-Driven))

八、深度总结:设计模式背后的核心思想

[1. 资源复用与全局受控 (单例模式)](#1. 资源复用与全局受控 (单例模式))

[2. 创建与使用的彻底分离 (工厂模式)](#2. 创建与使用的彻底分离 (工厂模式))

[3. 非侵入式的逻辑增强 (代理模式)](#3. 非侵入式的逻辑增强 (代理模式))

[4. 求同存异的兼容艺术 (适配器模式)](#4. 求同存异的兼容艺术 (适配器模式))

[5. 把选择权交给运行时 (策略模式)](#5. 把选择权交给运行时 (策略模式))

[6. 流程标准化与细节定制化 (模板模式)](#6. 流程标准化与细节定制化 (模板模式))

[7. 组件间的极致松耦合 (观察者模式)](#7. 组件间的极致松耦合 (观察者模式))

九、总结对照表


Spring 框架核心设计模式深度解析

本文档旨在对 Spring 框架中最核心的七大设计模式进行深度剖析。我们将遵循"原理 -> 基础实现 -> 进阶变体 -> Spring 源码落地"的逻辑路径,不仅解释"它是怎么做的",更要探讨"为什么这么做",全面揭示 Spring 强大扩展性背后的代码哲学。

一、单例模式 (Singleton Pattern)

单例模式是 Spring 框架中最基础、应用最广泛的模式。它的核心思想是保证一个类在整个系统中只有一个实例,并提供一个全局访问点。

1. 基础形态:饿汉式 vs 懒汉式

单例模式的实现核心在于"实例创建的时机"。

  • 饿汉式 (Eager Initialization): 类加载时立即创建。

    • 优点: 线程天生安全(由 JVM 保证),执行效率高(无锁)。

    • 缺点: 无论是否使用,实例都会占用内存,可能造成资源浪费。

  • 懒汉式 (Lazy Initialization): 第一次使用时才创建。

    • 优点: 资源利用率高。

    • 缺点: 必须处理多线程并发安全问题。

2. 进阶形态:双重检查锁定 (DCL) 与 静态内部类

在生产环境中,我们通常追求既延迟加载又线程安全的实现。

  • 双重检查锁定 (Double-Checked Locking):

    这是懒汉式的主流优化方案。

    java 复制代码
    public class Singleton {
        // volatile 禁止指令重排序,防止读取到半初始化对象
        private static volatile Singleton instance;
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) { // 第一重检查:避免不必要的加锁
                synchronized (Singleton.class) {
                    if (instance == null) { // 第二重检查:确保并发安全
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
  • 静态内部类 (Static Inner Class):

    这是一种更优雅的 Java 写法,利用了 JVM 类加载机制来实现延迟加载和线程安全,无需使用 synchronized。

    java 复制代码
    public class Singleton {
        private Singleton() {}
    
        // 只有当 getInstance 被调用时,Holder 才会加载,INSTANCE 才会初始化
        private static class Holder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return Holder.INSTANCE;
        }
    }

3. Spring 中的终极落地

Spring 中的单例模式与 GoF 原生模式有一个本质区别:Spring 的单例是"容器级"的,而非"ClassLoader 级"的。 也就是说,一个 Spring 容器中,某个 Bean 默认只有一个实例。

3.1 核心实现:单例注册表

Spring 并没有把 Bean 写成单例类,而是通过一个注册表(Registry)来管理这些对象。这个注册表本质上就是一个巨大的缓存池。

  • 源码核心类: DefaultSingletonBeanRegistry

  • 核心数据结构:

    java 复制代码
    // 一级缓存:存放已经完全初始化好的单例 Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 三级缓存机制(用于解决循环依赖):
    // singletonFactories (三级): 存放 Bean 工厂,用于提前暴露对象
    // earlySingletonObjects (二级): 存放半成品对象
3.2 获取单例的逻辑 (getSingleton)

当调用 getBean 时,Spring 会尝试从缓存中获取:

  1. 先去 singletonObjects(一级缓存)拿。

  2. 拿不到,且当前 Bean 正在创建中(循环依赖场景),去 earlySingletonObjects(二级缓存)拿。

  3. 还拿不到,去 singletonFactories(三级缓存)拿,并将其提升到二级缓存。

  4. 如果都拿不到,才开始真正的创建流程(CreateBean),并在创建完成后放入一级缓存。

3.3 线程安全与并发

Spring 容器在启动时(refresh 方法),默认会使用主线程将所有非懒加载的单例 Bean 全部初始化(Pre-instantiation)。这意味着在应用真正对外提供服务前,Bean 已经创建好了,因此在运行时获取 Bean 是线程安全的(直接从 Map 读)。

  • 注意: 虽然获取 Bean 是安全的,但 Bean 自身的状态(成员变量)如果可变,依然需要开发者自己保证线程安全。这也是为什么 Spring 建议 Bean 尽量设计为无状态的(Stateless)。

二、工厂模式 (Factory Pattern)

工厂模式是 Spring IoC(控制反转)容器的基石,也是将代码从"面向实现"转向"面向接口"的关键一步。它的核心思想是将"对象的创建过程"与"对象的使用过程"彻底分离,从而降低系统的耦合度。

1. 基础形态:简单工厂 (Simple Factory)

这也是最直观的工厂写法,虽然不属于 GoF 23 种设计模式,但在小型项目或工具类中应用极广。

  • 实现方式: 定义一个静态方法,根据传入的参数(如字符串名称或枚举),利用 switch-caseif-else 分支决定创建哪个具体的子类对象。

  • 代码示例:

java 复制代码
public class SimpleFactory {
    // 静态方法,由外部直接调用
    public static Product createProduct(String type) {
        if ("A".equals(type)) {
            // 这里可能包含 ProductA 复杂的初始化逻辑
            return new ProductA();
        } else if ("B".equals(type)) {
            return new ProductB();
        }
        // 如果类型无法识别,通常抛出异常或返回 null
        throw new IllegalArgumentException("Unknown product type: " + type);
    }
}
  • 缺点分析: 这种写法最大的问题是违背了设计模式中的"开闭原则"(Open/Closed Principle)。每当业务扩展需要新增一种 ProductC 时,你都不得不修改 SimpleFactory 的源代码(增加一个新的 case 分支)。在大型系统中,这种频繁的源码修改是导致 bug 滋生的温床。

2. 进阶形态:工厂方法模式 (Factory Method)

这是 GoF 标准模式。它通过多态性解决了简单工厂的扩展性问题,将"实例化"的责任推迟到了具体的子类工厂中进行。

  • 核心逻辑: 定义一个创建对象的抽象接口,但由子类决定要实例化的类是哪一个。客户端只依赖于抽象工厂,而不关心具体是哪个工厂在工作。

  • 代码示例:

java 复制代码
// 抽象工厂接口
public interface Factory {
    Product create();
}

// 具体工厂A:专门负责生产 ProductA
public class FactoryA implements Factory {
    @Override
    public Product create() {
        return new ProductA();
    }
}

// 具体工厂B:专门负责生产 ProductB
public class FactoryB implements Factory {
    @Override
    public Product create() {
        // 假如 ProductB 的创建需要依赖数据库连接或配置文件
        // 这些复杂的初始化逻辑都可以封装在这里,而不污染客户端代码
        return new ProductB(); 
    }
}

3. Spring 中的终极落地

Spring 将工厂模式运用到了极致,主要体现在两个层面:一个是宏观的容器管理,一个是微观的特殊 Bean 创建。

3.1 容器级工厂:BeanFactory

Spring 的 BeanFactory 接口是典型的工厂模式体现,它是 Spring IoC 容器的顶层接口。

  • 机制: 当你调用 getBean("userService") 时,Spring 容器就是一个巨大的工厂。它不仅仅是 new 一个对象那么简单,它根据 BeanDefinition(配方)生产出 Bean 实例,并负责管理其完整的生命周期。

  • Spring 源码逻辑(简化版):

    java 复制代码
    // AbstractBeanFactory.java
    public Object getBean(String name) {
        return doGetBean(name, null, null, false);
    }
    
    // doGetBean 内部包含了极其复杂的对象创建流水线:
    // 1. 检查一级、二级、三级缓存,解决循环依赖问题
    // 2. 实例化对象 (Instantiation)
    // 3. 属性填充 (Populate Bean - 依赖注入发生在这里)
    // 4. 初始化 (Initialization - 调用 init-method, PostConstruct 等)
    // 5. 注册销毁回调
3.2 用户级工厂:FactoryBean 接口(重点)

这是 Spring 提供给开发者的一种**"作弊代码"**,也是很多开源框架(如 MyBatis, Feign, Dubbo)整合 Spring 时的首选扩展点。

  • 痛点: 有些对象的创建过程极其复杂,不是简单的 new 就能搞定。例如 MyBatis 的 SqlSessionFactory,需要解析 XML 配置文件、配置数据源、构建 Configuration 对象、扫描 Mapper 接口等。如果强制用 XML <bean>@Bean 注解配置,代码会显得非常臃肿且难以维护。

  • 解决方案: 实现 FactoryBean 接口。Spring 容器在初始化时,如果发现一个 Bean 实现了这个接口,它通过 getBean 返回的就不是这个 Bean 本身,而是它"生产"出来的对象。

  • 代码示例:

    java 复制代码
    // 这是一个工厂 Bean,但它生产的是 ComplexObject
    public class MyComplexObjectFactory implements FactoryBean<ComplexObject> {
    
        @Override
        public ComplexObject getObject() throws Exception {
            ComplexObject obj = new ComplexObject();
            // 这里可以写几百行复杂的初始化代码,比如建立 TCP 连接
            obj.connectToRemote();
            obj.optimize();
            return obj;
        }
    
        @Override
        public Class<?> getObjectType() {
            return ComplexObject.class;
        }
    
        @Override
        public boolean isSingleton() {
            return true; // 控制生产的对象是否是单例
        }
    }
  • 源码细节与使用技巧: * 当你配置了 id="myComplexObjectFactory" 的 Bean 时,调用 getBean("myComplexObjectFactory") 返回的是 getObject() 方法产出的 ComplexObject 对象。

    • 如果你真的想要获取这个工厂对象本身 (例如为了查看其状态),需要在 Bean 名称前加一个 & 前缀,即 getBean("&myComplexObjectFactory")

三、代理模式 (Proxy Pattern)

代理模式是 Spring AOP(面向切面编程)的灵魂。它的核心在于:在不改变原有业务逻辑源码的情况下,动态地增强对象的功能。 这就是我们常说的"非侵入式"开发。

1. 基础形态:静态代理 (Static Proxy)

  • 特点: 代理类和目标类实现相同的接口。代理类内部持有目标类的引用。

  • 缺点: 必须手动编写代理类。如果有 100 个不同的 Service 类都需要加日志功能,你就得写 100 个对应的代理类,或者修改现有的 100 个类。这会导致严重的类爆炸问题,维护成本极高。

2. 进阶形态:JDK 动态代理 (JDK Dynamic Proxy)

  • 实现原理: 基于 Java 的反射机制。利用 Proxy.newProxyInstance 方法,在程序运行时动态在内存中构建出代理类的字节码。

  • 硬性要求: 目标类必须实现至少一个接口。 因为 JDK 生成的代理类默认继承了 Proxy 类,由于 Java 是单继承的,所以它只能通过实现接口来伪装成目标对象。

  • 代码骨架:

    java 复制代码
    public class JdkProxyHandler implements InvocationHandler {
        private Object target; // 真实的业务对象
    
        public JdkProxyHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(">>> [AOP] 开启事务 / 记录日志"); // 前置增强
    
            // 利用反射调用目标对象的真实方法
            Object result = method.invoke(target, args); 
    
            System.out.println(">>> [AOP] 提交事务 / 资源清理"); // 后置增强
            return result;
        }
    }

3. 进阶形态:CGLIB 动态代理

  • 实现原理: 基于 ASM 底层字节码框架。它不通过接口,而是直接在运行时动态生成目标类的子类,并重写父类的非 final 方法。

  • 适用场景: 目标类没有实现接口,或者是普通的类。

  • 限制: * 目标类不能是 final 的(因为无法被继承)。

    • 目标方法不能是 finalstatic 的(因为无法被重写拦截)。

4. Spring 中的 AOP 源码实现

Spring 在 DefaultAopProxyFactory 中通过策略模式来智能决定使用哪种代理方式,尽量保证功能的全面性与性能的平衡:

java 复制代码
// DefaultAopProxyFactory.java 源码片段解析
public AopProxy createAopProxy(AdvisedSupport config) {
    // 逻辑判断顺序:
    // 1. config.isOptimize(): 是否开启了特定的优化(很少用)
    // 2. config.isProxyTargetClass(): 是否在配置中强制指定了 proxy-target-class="true"
    // 3. hasNoUserSuppliedProxyInterfaces(): 目标类是否根本没有实现任何接口
    
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        // 如果满足上述任一条件,使用 CGLIB 代理
        return new ObjenesisCglibAopProxy(config); 
    } else {
        // 默认情况下,如果目标类实现了接口,使用 JDK 动态代理
        return new JdkDynamicAopProxy(config); 
    }
}
  • SpringBoot 的默认行为变化: 值得注意的是,在 SpringBoot 2.x 之后,为了解决代理对象在类型转换时的异常(比如 UseService 接口代理后无法强转回 UserServiceImpl),SpringBoot 默认将 proxy-target-class 设置为 true,即倾向于默认使用 CGLIB,除非你显式配置使用 JDK 代理。

  • 常见坑点:自调用失效

    如果在同一个类中,方法 A 调用了方法 B,而方法 B 上配置了 AOP(如 @Transactional),通过 this.B() 调用时事务是不会生效的。因为 this 指代的是目标对象本身,而不是代理对象,从而绕过了增强逻辑。

四、适配器模式 (Adapter Pattern)

适配器模式的核心作用是**"翻译""兼容"**。它让两个原本接口不兼容的类可以协同工作,就像电源适配器将 220V 电压转换为电脑需要的 20V 一样。

1. 基础形态:对象适配器

通过组合(Composition)的方式,将一个既有的对象包装进适配器中,从而实现目标接口。

java 复制代码
// 目标接口:客户端只认识这个接口,需要一个 5V 的电压
interface DC5 {
    int output5V();
}

// 现有类(Adaptee):只有 220V 的电压输出能力
class AC220 {
    public int output220V() { return 220; }
}

// 适配器(Adapter):实现目标接口,持有现有类的引用
class PowerAdapter implements DC5 {
    private AC220 ac220; // 组合现有类

    public PowerAdapter(AC220 ac220) { this.ac220 = ac220; }

    @Override
    public int output5V() {
        int src = ac220.output220V();
        // 适配逻辑:进行降压转换
        return src / 44; 
    }
}

2. Spring 中的宏大叙事:HandlerAdapter

Spring MVC 是适配器模式的教科书级应用。

  • 背景: Spring MVC 极其灵活,它的 Controller(处理器)写法非常多变:

    1. 实现传统的 Controller 接口。

    2. 实现 HttpRequestHandler 接口(用于静态资源或简单服务)。

    3. 使用 @RequestMapping 注解的 POJO 方法(最常用,即 HandlerMethod)。

  • 问题: DispatcherServlet(核心前端控制器)如何调用这些五花八门的 Controller?它不能在核心代码里写死一堆 if (handler instanceof Controller) ... else if ...,这不符合开闭原则。

  • Spring 的解法: 定义统一的适配器接口 HandlerAdapter,通过适配器来屏蔽不同处理器之间的差异。

java 复制代码
public interface HandlerAdapter {
    // 1. 也就是策略模式的匹配:你能不能处理这个 handler?
    // 例如 RequestMappingHandlerAdapter 专门处理 @RequestMapping 的方法
    boolean supports(Object handler);

    // 2. 统一调用动作:不管那个 handler 内部多复杂,你最后必须给我返回 ModelAndView
    // 适配器负责反射调用 Controller 的具体方法,并处理参数绑定、返回值封装
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
  • 执行流程详解:

    1. 请求到达: 用户发送 HTTP 请求,到达 DispatcherServlet

    2. 查找处理器: DispatcherServlet 根据 URL 找到对应的 Handler(如一个 Controller 的方法)。

    3. 查找适配器: 遍历所有注册的 HandlerAdapter,调用 supports()。比如对于 @RequestMapping 方法,会找到 RequestMappingHandlerAdapter

    4. 执行逻辑: 调用 adapter.handle()。适配器内部会处理复杂的参数解析(@RequestParam, @RequestBody),执行业务逻辑,最后统一返回结果。

    这就是为什么你可以随意切换 Controller 的写法(甚至可以是 Servlet),而不需要修改 DispatcherServlet 的任何核心代码。

五、策略模式 (Strategy Pattern)

策略模式核心在于**"算法的动态切换"**。它定义了一系列算法,并将每个算法封装起来,使它们可以互换。这让算法的变化独立于使用算法的客户。

1. 基础实现

通常包含三个角色:Strategy(抽象策略接口)、ConcreteStrategy(具体策略实现)、Context(上下文,持有接口引用)。

java 复制代码
// 1. 策略接口
interface PaymentStrategy {
    void pay(int amount);
}

// 2. 具体策略
class AlipayStrategy implements PaymentStrategy {
    public void pay(int amount) { System.out.println("支付宝支付:" + amount); }
}

class WechatStrategy implements PaymentStrategy {
    public void pay(int amount) { System.out.println("微信支付:" + amount); }
}

// 3. 上下文
class OrderService {
    // 可以通过构造函数或 Setter 注入具体的策略
    public void processOrder(PaymentStrategy strategy) {
        // 业务逻辑...
        // 这里的调用是多态的,无需写 if(type == "alipay") else ...
        strategy.pay(100);
    }
}

2. Spring 中的资源加载 (Resource)

Spring 需要加载各种资源(XML、Properties、图片)。资源可能在 classpath 下,也可能在文件系统中,或者在 URL 上。

  • 策略接口: Resource,定义了 getInputStream(), exists(), getDescription() 等统一方法。

  • 具体策略:

    • ClassPathResource: 针对类路径下的资源(classpath:)。

    • FileSystemResource: 针对文件系统中的资源(file:)。

    • UrlResource: 针对网络上的资源(http:, https:)。

    • ByteArrayResource: 针对内存中的字节数组。

  • 上下文(Context): ResourceLoader。当你调用 resourceLoader.getResource("classpath:config.xml") 时,Spring 内部会解析路径前缀 classpath:,并自动选择 ClassPathResource 策略来处理后续的 IO 操作。

3. Spring 中的 Bean 实例化 (InstantiationStrategy)

这是更底层的应用。Spring 在创建 Bean 时,到底是用 Constructor.newInstance()(标准的 JDK 反射),还是用 CGLIB 生成子类?

  • Spring 定义了 InstantiationStrategy 接口。

  • 默认实现: SimpleInstantiationStrategy。它使用反射来实例化 Bean。

  • 特殊情况: 如果 Bean 配置了 <lookup-method><replaced-method>(方法注入),Spring 需要重写 Bean 的方法。此时,策略会切换为 CglibSubclassingInstantiationStrategy,通过生成子类的方式来实现实例化。

六、模板模式 (Template Method Pattern)

模板模式的核心在于**"复用骨架,定制细节"**。它遵循"好莱坞原则"(Don't call us, we'll call you)。父类控制整个流程的控制权,只将特定的步骤钩子(Hook)留给子类实现。

1. 经典继承式模板

java 复制代码
abstract class AbstractGame {
    // 模板方法,final 禁止重写,保证核心流程不被篡改
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }
    // 留给子类实现的钩子方法
    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();
}

2. Spring 的变体:模板 + 回调 (Template + Callback)

经典的模板模式依赖继承,但 Java 是单继承的,且继承体系过深会导致系统僵化。Spring 创造性地结合了回调接口,让你不需要继承就能享受模板模式的红利。

JdbcTemplate 为例,这是 Spring 对 JDBC 操作的封装:

  • 模板部分(JdbcTemplate): 负责那些冗余、易错且标准的流程:

    1. 从 DataSource 获取数据库连接。

    2. 创建 PreparedStatement。

    3. 统一异常处理: 将 SQL 异常(Checked Exception)转换为 Spring 的 DataAccessException(Runtime Exception),这是 Spring DAO 层的一大亮点。

    4. 执行核心 SQL(这是变化的)

    5. 释放资源:无论成功失败,确保 ResultSet, Statement, Connection 依次关闭,防止内存泄漏。

  • 回调部分(RowMapper / PreparedStatementSetter):

    开发者只需要实现这一小部分逻辑,比如如何将 ResultSet 的一行数据映射为一个 Java 对象。

  • 代码伪逻辑演示:

    java 复制代码
    class JdbcTemplate {
        public <T> T query(String sql, RowMapper<T> rowMapper) {
            Connection con = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                con = dataSource.getConnection(); // 标准步骤
                stmt = con.createStatement();     // 标准步骤
                rs = stmt.executeQuery(sql);      // 标准步骤
    
                // --- 核心变化点,反向调用用户的回调代码 ---
                List<T> results = new ArrayList<>();
                int rowNum = 0;
                while(rs.next()) {
                    // "Don't call us, we'll call you"
                    results.add(rowMapper.mapRow(rs, rowNum++));
                }
                // -------------------------------------
    
                return results;
            } catch (SQLException ex) {
                // 异常转换逻辑
                throw translateException("StatementCallback", sql, ex);
            } finally {
                // 坚如磐石的资源释放
                JdbcUtils.closeResultSet(rs);
                JdbcUtils.closeStatement(stmt);
                JdbcUtils.closeConnection(con);
            }
        }
    }

这种 "Template Method with Callback" 的设计在 Spring 中无处不在,统一以 Template 结尾:RestTemplate, JmsTemplate, TransactionTemplate, RedisTemplate

七、观察者模式 (Observer Pattern)

观察者模式又称发布-订阅(Publish/Subscribe)模式。核心在于解耦。当一个对象状态改变时,通知所有依赖它的对象,而不需要知道这些对象是谁。

1. 基础实现

Java 原生提供了 Observable 类和 Observer 接口(但在 Java 9 中已被标记为废弃),现在的最佳实践通常是自己定义监听器接口,或者使用 Guava EventBus / Spring Event。

2. Spring 的事件驱动模型 (Event-Driven)

Spring 提供了一套完善的进程内事件机制,完全基于观察者模式。

  • 四大核心组件:

    1. Event (事件): 继承 ApplicationEvent,封装具体的业务数据。

    2. Listener (监听器/观察者): 实现 ApplicationListener 接口或使用 @EventListener 注解。

    3. Publisher (发布者): ApplicationContext 继承了 ApplicationEventPublisher 接口,具备发布能力。

    4. Multicaster (广播器): ApplicationEventMulticaster,这是幕后黑手,负责管理所有注册的监听器,并将事件准确派发给它们。

  • 工作流程源码解析:

    1. 注册阶段: 容器启动时,AbstractApplicationContext 会初始化 SimpleApplicationEventMulticaster,并将所有的 ApplicationListener Bean 注册进去(通常存储在一个 Set 集合中)。

    2. 发布阶段: 业务代码调用 context.publishEvent(new OrderSuccessEvent(order))

    3. 广播阶段: Multicaster 内部会遍历监听器列表,逐个调用。

    java 复制代码
    // SimpleApplicationEventMulticaster.java 伪代码
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener listener : getApplicationListeners(event)) {
            // 关键点:判断是否存在 Executor (线程池)
            Executor executor = getTaskExecutor();
            if (executor != null) {
                // 异步执行:观察者在单独线程跑,主线程立即返回,适合发送邮件/短信等耗时操作
                executor.execute(() -> listener.onApplicationEvent(event));
            } else {
                // 同步执行:默认情况。所有监听器执行完,publishEvent 方法才返回。
                // 这意味着如果监听器抛出异常,可能会阻断主流程(取决于事务配置)。
                listener.onApplicationEvent(event);
            }
        }
    }
  • 进阶技巧:事务绑定事件

    使用 @TransactionalEventListener 注解,可以控制监听器在当前事务的哪个阶段执行(例如:仅在事务提交成功后 AFTER_COMMIT 才发送通知)。这在处理"注册成功后发优惠券"等场景时非常关键,能确保数据一致性。

八、深度总结:设计模式背后的核心思想

如果说具体的代码实现是"招式",那么设计模式背后的核心思想就是"内功"。Spring 之所以能成为 Java 世界的长青树,是因为它深刻地领悟并实践了以下核心软件工程原则:

1. 资源复用与全局受控 (单例模式)

  • 核心思想: "收敛"

  • 解读: 在企业级应用中,混乱的对象创建是性能杀手和 bug 之源。Spring 通过容器级的单例模式,强行收敛了对象的创建权。它告诉我们:对于无状态的服务组件,全局一份足矣。 这不仅是出于性能考虑(减少 GC),更是为了确保全局状态(如配置、缓存)的一致性。

2. 创建与使用的彻底分离 (工厂模式)

  • 核心思想: "解耦"

  • 解读: 这是控制反转 (IoC) 的灵魂。使用者只需要声明"我想要什么",而不需要关心"它怎么来"。这种分离使得组件的替换变得异常简单(例如从 MySQL 切换到 Oracle,或者在测试时替换为 Mock 对象),极大地提升了系统的可维护性。

3. 非侵入式的逻辑增强 (代理模式)

  • 核心思想: "隔离"

  • 解读: 业务逻辑应该是纯粹的。记录日志、管理事务、权限校验这些"脏活累活",不应该污染核心业务代码。代理模式通过在运行时动态"套壳",实现了关注点分离 (Separation of Concerns)。它保护了业务代码的纯洁性,同时赋予了它强大的附加能力。

4. 求同存异的兼容艺术 (适配器模式)

  • 核心思想: "包容"

  • 解读: 现实世界是复杂的,标准永远在变。HandlerAdapter 的存在证明了:框架不应该强迫用户改变,而应该改变自己去适应用户。 只要你能完成任务,无论你是哪种形式的 Controller,Spring 都能通过适配器接纳你。这是 Spring 生态繁荣的关键。

5. 把选择权交给运行时 (策略模式)

  • 核心思想: "灵活"

  • 解读: 避免在代码中写死逻辑。通过定义统一的接口(Resource),Spring 允许系统在运行时根据环境(是本地文件还是网络 URL?)动态选择最合适的处理算法。这是开闭原则 (Open/Closed Principle) 的最佳实践:对扩展开放,对修改关闭。

6. 流程标准化与细节定制化 (模板模式)

  • 核心思想: "规范"

  • 解读: 好莱坞原则(Don't call us, we'll call you)。Spring 极其擅长定义标准流程(打开连接 -> 执行 -> 关闭),它把易错的、重复的样板代码锁死在模板里,只把那一点点变化的业务逻辑留给开发者填空。这既保证了程序的健壮性,又降低了开发者的心智负担。

7. 组件间的极致松耦合 (观察者模式)

  • 核心思想: "联动"

  • 解读: 系统越复杂,模块间的直接依赖就越危险。观察者模式通过"事件"这一中间媒介,打断了模块间的直接调用链。A 模块只管发消息,B、C、D 模块听到消息后各自干活,大家互不认识,却能完美配合。这是构建大型分布式系统的基石。

九、总结对照表

|-----------|----------------------------------|----------------------------------------------|
| 模式 | Spring 核心组件 | 一句话设计哲学 |
| 单例模式 | DefaultSingletonBeanRegistry | 资源共享:全局唯一,避免重复创建,集中管理状态。 |
| 工厂模式 | BeanFactory / FactoryBean | 封装复杂性 :不要让用户自己 new 对象,尤其是构建过程复杂的对象。 |
| 代理模式 | AOP / Transaction | 非侵入式增强:想加功能(日志/事务)?别改老代码,动态套一层壳即可。 |
| 适配器模式 | HandlerAdapter | 兼容并包:不管你长什么样(Controller写法),我都提供转换头让你能对接。 |
| 策略模式 | Resource / InstantiationStrategy | 拥抱变化:条条大路通罗马,运行时根据环境决定走哪条路。 |
| 模板模式 | JdbcTemplate / RestTemplate | 标准化流程:脏活累活框架干,核心业务逻辑你来填。 |
| 观察者模式 | ApplicationEvent | 极致解耦:你干你的,我干我的,有事发个广播就行,互不依赖。 |

深入理解这 7 种模式在 Spring 中的实现,你就不仅仅是在使用一个框架,而是在与一位顶级的架构师(Spring 的设计者们)进行跨时空的对话。

相关推荐
毕设源码-钟学长2 小时前
【开题答辩全过程】以 个性化电影推荐网站的设计与实现为例,包含答辩的问题和答案
java·spring boot
C++业余爱好者2 小时前
Power Job 快速搭建 及通信机制介绍
java
qq_2704900963 小时前
SpringBoot药品管理系统设计实现
java·spring boot·后端
、BeYourself3 小时前
SpringAI-ChatClient Fluent API 详解
java·后端·springai
星辰_mya3 小时前
reids哨兵集群与选主
java·开发语言
BD_Marathon3 小时前
SpringBoot快速入门
java·spring boot·后端
期待のcode3 小时前
Java的多态
java·开发语言
证能量少女4 小时前
2026大专Java开发工程师,考什么证加分?
java·开发语言
FPGAI4 小时前
Java学习之基础概念
java·学习