京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)

本文 的 原文 地址

本文 的 原文 地址

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的相关面试题:

说下spring中常用的设计模式?

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

最近有小伙伴在面 京东,问到了相关的面试题,可以说是逢面必问。

小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

本文作者:

  • 第一作者 老架构师 肖恩(肖恩 是尼恩团队 高级架构师,负责写此文的第一稿,初稿 )
  • 第二作者 老架构师 尼恩 (45岁老架构师, 负责 提升此文的 技术高度,让大家有一种 俯视 技术、俯瞰技术、 技术自由 的感觉

现状:能 暴击面官的 顶级高手,不到10%

Spring 的源码 非常重要,但是 很多人 , 在死记硬背。

总体而言, 90%的人回答不到 Spring 的源码 底层思维/ 底层原理

真正 能 做到 暴击面官的, 不到10%。

本文,尼恩从设计模式入手 , 带大家 穿透 Spring 的源码 , 帮大家 暴击面官。

一:Spring源码中 8大设计模式

Spring 框架中用到了多种设计模式,以下是一些常见的设计模式及其在 Spring 中的应用:

1、 模板模式(Template Pattern)

Spring 的数据访问模块(如 JdbcTemplate)使用了模板 模式。

JdbcTemplate 定义了数据库操作的基本流程(如连接数据库、执行 SQL 语句、处理结果集等),同时允许子类或使用者通过回调函数自定义特定的逻辑(如提供 SQL 语句、处理结果集的细节等)。

最为重要的是: 在 Spring 框架bean生命周期中的使用 模板模式(Template Pattern), 这个如果面试的时候能吹出来, 面试官一定 口水直流。

2、单例模式(Singleton Pattern)

Spring 中的 Bean 默认是单例模式。例如,当我们在 Spring 配置文件或注解中定义一个 Bean 时,Spring 容器会保证该 Bean 在整个应用中只有一个实例。这使得资源可以被共享和重复利用,提高了系统性能。

3、工厂模式(Factory Pattern)

Spring 提供了 BeanFactory 接口,它是一个工厂,负责创建和管理 Bean。通过 BeanFactory,我们可以根据 Bean 的名称或类型获取 Bean 实例。

4、代理模式(Proxy Pattern)

Spring AOP(面向切面编程)大量使用了代理模式。通过代理模式,Spring 可以在目标对象的方法调用前后插入切面逻辑,实现诸如日志记录、事务管理等功能。

5、 策略模式(Strategy Pattern)

Spring 中的 HandlerMapping 接口就是一个策略模式的应用。不同的 HandlerMapping 实现类可以定义不同的请求映射策略,例如根据 URL 路径映射、根据请求参数映射等。

6、 观察者模式(Observer Pattern)

Spring 的事件发布与监听机制使用了观察者模式。通过 ApplicationEventPublisher 发布事件,ApplicationListener 监听事件,当事件发生时,监听器可以执行相应的逻辑。

7、 装饰器模式(Decorator Pattern)

Spring AOP 中的代理类可以看作是装饰器模式的应用。通过代理类对目标对象进行包装,可以在不修改目标对象代码的情况下,增加额外的功能(如日志记录、事务管理等)。

8、适配器模式(Adapter Pattern)

Spring MVC 中的 HandlerAdapter 接口就是一个适配器模式的应用。不同的 HandlerAdapter 实现类可以将不同的控制器(Handler)适配到 Spring MVC 的处理流程中。

这些设计模式在 Spring 框架中相互配合,共同构成了 Spring 强大的功能和灵活的架构,使得开发者可以方便地进行开发和扩展。

二:第一大 Spring 模式: 模板模式

模板模式 ,是一种常用的编程设计模式。

模板模式 的核心思想是:分离变与不变, 把一些固定不变的操作放在一个“骨架”里,而把那些可能会变化的部分留给子类去实现。

Spring模板模式 有巨大的优越性

与 经典模版模式(或者传统模版模式) 比较, 在Spring模板模式 有巨大的优越性, 主要有三大点:

  • 解耦思想:将可变部分与不变部分分离,避免紧密耦合
  • 灵活扩展:允许运行时动态改变行为,而非编译时固化
  • 非侵入式:业务逻辑无需强制继承框架特定基类

"组合优于继承"的核心设计哲学,深刻 镂刻 和 体现在 Spring模板模式 中。

接下来, 尼恩团队 抽丝剥茧, 一点一点 给大家解开神秘的面试 , 带 大家 一点点 体验 Spring模板模式 和 Spring 源码的 思想和灵魂。

什么是 经典 的 模版模式?

在模板模式中,由抽象类定义模板方法和钩子方法,模板方法定义一套业务算法框架,算法框架中的某些步骤,由钩子方法负责完成。具体的子类可以按需要重写钩子方法。

image-20250612173204556

比如做面条的模版模式

image-20250612173234517

1、骨架方法(模板方法)

模板方法(Template Method):也常常被称为骨架方法,主要定义了整个方法需要实现的业务操作的算法框架。其中调用不同方法的顺序因人而异,而且这个方法也可以做成一个抽象方法要求子类自行定义逻辑流程。

模板方法 也叫骨架方法 ,它就像一份“操作指南”,定义了整个业务流程的大致步骤和顺序。

在代码中,这个骨架方法是由父类(抽象类)定义的。它会调用一些具体的步骤,而这些步骤可能是由子类来完成的。

比如,可以把它想象成做菜的步骤:先洗菜、再切菜、然后炒菜、最后装盘。这些步骤的顺序通常是固定的,但具体怎么洗、怎么切、怎么炒,可能因人而异, 交给 子类去实现。

2、钩子方法

钩子方法(Hook Method):钩子方法是被模板方法的算法框架所调用,而由子类提供具体的实现的方法。

钩子方法就是那些“具体步骤”。在父类中,它们是骨架方法的一部分,但具体内容需要由子类来提供。比如,在前面提到的做菜例子中,“怎么切菜”或“怎么炒菜”就可以看作是钩子方法。

在抽象父类中,钩子方法常常被定为一个空方法或者抽象方法,需要由子类去实现。

Hook Method 钩子方法的存在,可以让sub class 子类提供 某个 操作的具体事项,从而让 sub class 子类实现算法中 变动的部分。

3、 模板模式的实例代码

为了更好地理解模板模式,我们来看一个简单的例子。



@Slf4j
public class TemplateDemo
{

    static abstract class AbstractAction
    {
        /**
         * 模板方法:算法骨架
         */
        public void tempMethod()
        {
            log.info("---面条的模版方法---");
            beforeAction();
            action();
            afterAction();
        }

        /**
         * 执行前
         */
        protected abstract void beforeAction();


        /**
         * 钩子方法
         */
        public void action() {
            log.info("煮面条");
        };

        /**
         * 执行后
         */
        protected abstract void afterAction();
    }

    static class ActionA extends AbstractAction
    {
        @Override
        protected void beforeAction() {
            log.info("准备黄酱");
        }

        @Override
        protected void afterAction() {
            log.info("加入黄酱");
        }
    }

    static class ActionB extends AbstractAction
    {
        @Override
        protected void beforeAction() {
            log.info("准备番茄酱");
        }

        @Override
        protected void afterAction() {
            log.info("加入番茄酱");
        }
    }

    public static void main(String[] args)
    {
        AbstractAction action = null;

        //创建一个 ActionA 实例
        action=   new ActionA();
        //执行基类的模板方法
        action.tempMethod();

        //创建一个 ActionB 实例
        action=   new ActionB();
        //执行基类的模板方法
        action.tempMethod();


    }
}

这个例子演示了模板方法模式(Template Method Pattern)的应用。

模板方法模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。

模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。

对上面的案例,具体介绍如下:

1) 模板方法模式的体现

  • AbstractAction抽象类:定义了模板方法tempMethod(),它规定了算法(煮面条过程)的大致顺序,即先beforeAction()准备食材,然后action()煮面条,最后afterAction()添加调料。这体现了模板方法模式的核心,即定义一个算法的骨架。
  • action()方法(钩子方法):在这个例子中,action()方法被定义为一个具体的实现(煮面条),这在模板方法模式中被称为“钩子方法”,它提供了默认的行为,子类可以选择覆盖或者直接使用。

2) 子类的个性化实现

  • ActionA类:继承自AbstractAction,重写了beforeAction()afterAction()方法,分别实现了准备黄酱和加入黄酱的个性化操作,从而自定义了煮黄酱面的具体过程。
  • ActionB类:同样继承自AbstractAction,重写了beforeAction()afterAction()方法,实现了准备番茄酱和加入番茄酱的操作,定义了煮番茄酱面的过程。

3) 运行流程

  • main方法中,首先创建ActionA实例并调用其tempMethod()方法,根据ActionA的实现,输出了煮黄酱面的过程。
  • 然后创建ActionB实例并调用tempMethod(),输出了煮番茄酱面的过程。

4) 代码的优点

通过模板方法模式,将煮面条的通用流程和具体细节分离,抽象类定义了流程骨架,而具体细节由子类实现。

上面的案例,符合“好莱坞原则”——“不要调用我们,我们会调用”,提高了代码的可扩展性和复用性。

当需要新增一种面条的煮法时,只需添加一个新的子类继承AbstractAction并实现相应的方法,无需修改现有代码,符合开闭原则。

模板模式的核心思想

分离变与不变,是软件设计的一个基本原则。 模板模式的核心在于“分离变与不变”。也就是说:

  • 不变的部分(比如流程的顺序)被封装在父类的骨架方法中。
  • 变化的部分(比如具体的实现细节)通过钩子方法交给子类去处理。

模板模式将不变的部分封装在base class 基类的骨架方法中,而将变化的部分通过钩子方法进行封装,交给sub class 子类去提供实现,在一定程度上优雅的阐述了“分离变与不变”这一软件设计原则。

这种设计方式的好处是:

1、 提高代码的复用性:父类定义了通用的流程,子类只需要关注自己独特的部分。

2、 让代码更清晰:每个类的职责明确,父类负责整体流程,子类负责具体实现。

3、 更容易扩展:如果以后需要新增一种饮料,只需要创建一个新的子类即可,不需要修改现有的代码。

spring “模版模式“ 的 特殊性

我们经常在面试中会遇到这样一个问题:

能简单说一下Spring Bean的创建过程主要包含哪些步骤吗?

这个问题其实和一种设计模式——模板模式有关。

模板模式的核心思想是:定义一个操作的大致框架,把某些具体的步骤留给子类去实现。

而Spring通过这种模式,让开发者可以灵活地定制Bean的创建过程,这也体现了Spring的强大扩展性。

比如,利用模板模式,Spring能让用户定制Bean的创建过程。

如果仔细研究过Spring的源码,会发现,Spring对模板模式的实现方式和传统的实现方式有些不同。传统意义上的模板模式通常是基于抽象类来实现的,但Spring并没有完全按照这种方式来做,而是采用了一种类似回调机制的方式来实现。

传统模板模式的特点

在传统的模板模式中,通常会有两个关键部分:

1、 基类中的骨架逻辑:这是由父类提供的固定流程,定义了整个操作的基本步骤。

2、 钩子方法(Hook Method):这些方法的具体实现由子类提供,用来定制某些具体的行为。

传统意义的模版模式 两个 部分:基类 钩子方法抽象(Hook Method Abstraction ) 和 子类的 “钩子方法 实现” ( Hook Method Implementation )。

例如,在传统模板模式中,父类可能定义了一个方法execute(),它包含了几个步骤,其中某些步骤是抽象的,需要子类去实现。

Spring中模板模式的不同之处

创建 spring bean 场景中,也用了 模版模式 ,但是 已经 不是传统意义的 钩子方法实现, 做了大的变形。

创建 spring bean 场景 的模版模式 两个 部分:模板(BeanFactory)抽象基类 , Hook 钩子 对象 。

  • 在创建 spring bean时 ,模板(BeanFactory)变成了 执行的骨架逻辑。 在这里, 传统的 “钩子方法(Hook Method)抽象” 变成了 spring 的 “(BeanFactory)模板工厂”。
  • 而 需要子类实现 的 具体逻辑, 也就是将要执行的函数实现,被 封装成Hook 钩子 对象(比如,初始化方法封装成 InitializingBean 对象)。 传统的 钩子方法的实现(Hook implement ) 变成了 钩子对象 (Hook Object)。
  • 最终, Hook Object 传递给模板(BeanFactory)来执行。

比如: 调用自定义 init-method 也是钩子对象,是 通过反射封装 的 钩子对象

比如: 销毁逻辑也被封装为对象DisposableBean,也是 通过反射封装 的 钩子对象, 调用自定义 destroy-method。

虽然 Spring中 模板模式的应用稍微复杂一些,但它依然遵循“定义骨架逻辑 + 提供定制点”的核心思想。

Spring的实现方式有以下特点:

1、 骨架逻辑由BeanFactory提供

在Spring中,BeanFactory扮演了模板的角色,它负责定义Bean创建的整体流程。这个流程是一个固定的骨架逻辑,比如加载Bean定义、实例化Bean、初始化Bean等。

2、 定制点被封装为“钩子对象”

和传统模板模式不同的是,Spring并没有直接要求子类去实现某些方法,而是将这些 定制点 封装成了独立的对象。这些对象被称为“钩子对象”,它们会被传递给BeanFactory来执行。

Spring 模板模式 变形记:从“钩子方法”到“钩子对象”

在传统的模板模式中,通常会用一个 抽象基类 来定义整个逻辑的框架(也就是骨架),然后让具体的 子类 通过实现一些特定的方法(我们叫它“钩子方法”)来补充具体的业务逻辑。

举个例子,就像盖房子一样,抽象基类负责设计房子的整体结构(比如几层楼、房间布局等),而具体的子类则负责填充细节(比如选择用什么材料、刷什么颜色的漆)。这样,不同的子类就可以根据自己的需求,实现不同的功能,但整体流程是固定的。

不过,在 Spring 的体系中,模板模式的应用方式稍微有点不一样。为了让大家更清楚地理解两者的区别,我们可以用下面这个表格来对比一下:

传统模板模式Spring 的变体
抽象基类(定义骨架)AbstractBeanFactory(模板)
子类实现钩子方法钩子对象(如 InitializingBean
方法调用(子类 → 父类)对象回调(模板 → 钩子对象)

简单来说,传统模板模式更多依赖于继承,子类通过实现钩子方法来定制逻辑;

而 Spring 的模板模式则是通过协作对象(钩子对象)来实现类似的功能,这种方式更加灵活,也更符合现代编程中的“组合优于继承”的设计理念。

1、 传统模板模式

在传统的方式中,我们会先定义一个抽象基类,这个基类里包含了整个逻辑的框架(也就是骨架)。然后,具体的子类需要去实现一些预留的方法(钩子方法),这些方法会在骨架逻辑中被调用。换句话说,子类通过继承抽象基类,并实现钩子方法,来控制具体的行为。

2、 Spring 的变体

在 Spring 中,模板模式的设计思路发生了变化。它不再依赖于继承关系,而是通过 协作对象 来完成任务。

具体来说:

  • Spring 提供了一个模板类(比如 JdbcTemplateRestTemplate),这个模板类负责处理核心逻辑。

  • 如果需要自定义某些行为,可以提供一个 钩子对象(比如实现某个回调接口,或者传入一个策略类)。模板类会在执行过程中调用这个钩子对象的方法。

  • 这种方式的好处是,不需要通过继承来扩展功能,而是通过组合的方式来实现灵活性。

1、Spring 的模板模式核心:AbstractBeanFactory

AbstractBeanFactory 是 Spring Bean 创建的模板基类,它定义了完整的 Bean 生命周期流程(如实例化、属性注入、初始化),但具体逻辑交给“钩子对象”执行,而不是子类实现。

AbstractBeanFactory 是一个关于 Spring Bean 创建 的基础类,可以把它理解为一个“通用模板”。
这个模板类的主要作用是规定了 Bean 从无到有的整个生命周期过程,比如:

  • 创建对象实例(也就是把 Bean 给“造出来”),

  • 给属性赋值(把需要的数据填进去),

  • 完成初始化工作(让 Bean 变得可以正常使用)。

不过需要注意的是,这个模板类本身并不会直接去实现这些具体的操作步骤。

相反,它会把具体的执行任务交给一些专门设计的“工具对象”(我们叫它们“钩子对象”)来完成。

换句话说,模板类负责规划流程,而具体的干活儿的事情则由这些“钩子对象”来负责。

这样设计的好处是,可以让不同的 Bean 按照自己的需求灵活地实现各自的逻辑,而不需要每个 Bean 都重新定义整个生命周期的过程。 也更符合现代编程中的“组合优于继承”的设计理念。

关键源码(doCreateBean 流程)

// AbstractAutowireCapableBeanFactory.java(继承自 AbstractBeanFactory)
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化 Bean(模板逻辑)
    Object bean = createBeanInstance(beanName, mbd, args); 

    // 2. 属性注入(模板逻辑)
    populateBean(beanName, mbd, instanceWrapper); 

    // 3. 初始化(调用钩子对象)
    bean = initializeBean(beanName, bean, mbd); 
    return bean;
}

2、 “钩子对象”如何替代“钩子方法”?

Spring 将传统意义上的“钩子方法实现” 封装成对象,由模板(AbstractBeanFactory)在适当时机回调这些对象。

(1)初始化阶段的钩子对象:InitializingBean
  • 传统模式:子类实现 init() 方法。
  • Spring 模式:Bean 实现 InitializingBean 接口,其 afterPropertiesSet() 方法作为钩子逻辑。

Spring 把传统意义上用“钩子方法”实现的功能,包装成了一个个独立的对象。然后,通过模板机制,在合适的时机把这些对象调用回来。

简单来说,以前我们可能需要通过写一些特定的方法(钩子方法)来让程序在某个时刻执行特定逻辑。

但现在,Spring 帮我们把这种逻辑抽出来,变成一个单独的对象。这样做的好处是,代码更清晰、更容易管理。

等到需要执行这个逻辑的时候,Spring 内部的模板会自动找到并调用这些对象,完成对应的操作。

源码示例


// AbstractAutowireCapableBeanFactory#invokeInitMethods
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) {
    // 1. 调用 InitializingBean 的钩子方法(钩子对象)
    if (bean instanceof InitializingBean) {
        ((InitializingBean) bean).afterPropertiesSet();
    }

    // 2. 调用自定义 init-method 也是钩子对象,通过反射封装)
    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        Method initMethod = mbd.getInitMethod();
        initMethod.invoke(bean); // 反射调用
    }
}

(2)销毁阶段的钩子对象:DisposableBean

同理,销毁逻辑也被封装为对象DisposableBean:


// DisposableBeanAdapter.java(封装销毁逻辑)
public void destroy() {
    if (this.bean instanceof DisposableBean) {
        ((DisposableBean) this.bean).destroy(); // 钩子对象回调
    }
    // 调用自定义 destroy-method
}

同样地,和创建逻辑类似,当需要处理“销毁”相关的事情时,也专门设计了一个东西,它的名字叫 DisposableBean

可以把它理解成一个专门负责“清理工作”的工具或者助手。比如,当某个对象完成了它的任务,不再需要的时候,这个 DisposableBean 就会出场,帮助我们把相关的资源释放掉,确保系统不会因为忘记清理而变得混乱或者浪费资源。

DisposableBean 是一个“收尾专家”,专门负责在结束阶段做好清理和善后工作。

3、 为什么 Spring 用“钩子对象”而非“钩子方法”?

解耦: 组合优于继承的思想

在传统的开发方式中,子类必须通过继承基类来实现功能。而在 Spring 框架中,任何普通的 Java 对象(也就是我们常说的 POJO),只要实现了某个接口,就可以直接作为功能对象使用。比如说,不需要让一个类去继承某个特定的父类,只需要让它实现某个接口,就能完成相应的功能。

  • 传统模式需要子类继承基类,而 Spring 通过 组合实现的模版模式, 比如 允许任何 POJO 通过实现接口(如 InitializingBean)成为钩子对象。

  • 例如,UserService 无需继承 AbstractBeanFactory,只需实现 InitializingBean

灵活性

  • 钩子逻辑可以动态注册(如 @PostConstruct 通过 BeanPostProcessor 处理)。
  • 对比传统模式,子类必须提前定义好钩子方法。

可扩展性

  • Spring 可以支持多种钩子对象(如 Aware 接口、BeanPostProcessor),比如,可以通过实现不同的接口,来定义各种各样的功能模块。
  • 而传统模式受限于单一继承。

4、Spring 模板模式的”组合优于继承思想“

传统模式受限于单一继承,大大提高了 模版模式的 扩展性。

而相比之下, Spring 可以支持多种钩子对象(如 Aware 接口、BeanPostProcessor),能够支持多种扩展方式,这种设计大大增强了模板模式的灵活性和可扩展性,让开发者可以根据实际需求更方便地进行功能扩展。

Spring 的模板模式实现 ,可谓是太优秀了

**传统模式示例: 通过 继承 实现 **

abstract class AbstractTemplate {
    // 骨架方法
    public void templateMethod() {
        step1();
        hookMethod(); // 子类实现
        step2();
    }
    protected abstract void hookMethod();
}

// 钩子实现, 要继承  模版类
class ConcreteImpl extends AbstractTemplate {
    @Override
    protected void hookMethod() {
        System.out.println("子类的钩子方法");
    }
}

**Spring 模式示例: 通过 组合 实现 **

class BeanFactoryTemplate {
    // 骨架方法
    public void createBean() {
        instantiate();
        invokeHookObjects(); // 回调钩子对象
        destroy();
    }

    private void invokeHookObjects() {
        for (HookObject hook : hooks) {
            hook.execute(); // 例如:initializingBean.afterPropertiesSet()
        }
    }
}

// 钩子对象,无需继承模板类 
class InitializingBeanImpl implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        System.out.println("钩子对象的逻辑");
    }
}

5、 总结:Spring 模板模式的核心思想

模板:比如,通过AbstractBeanFactory 定义 Bean 生命周期的骨架流程。

钩子对象:

  • 接口, 如 InitializingBeanDisposableBean
  • 注解,如 @PostConstruct@PreDestroy(最终被封装成对象)。

执行方式:模板通过回调钩子对象(而非子类方法)实现扩展。

好莱坞原则 的妙处

这种设计的好处是,Spring 的核心流程保持不变,非常稳定。

与此同时,基于 好莱坞原则 思想 + 钩子接口 ,又可以通过实现 “钩子对象” 插入自定义逻辑,想怎么扩展就怎么扩展。

这种方式完美体现了“好莱坞原则”——简单来说,就是“不用主动找我们,我们会主动来找”。

换句话说,Spring 会主动调用定义的扩展逻辑,而不需要去修改它的核心代码。

三:通过 init-method 源码 ,对 “组合优于继承” 做 深度解析

1. "组合优于继承"的本质

在Spring模板模式中,"组合优于继承"的核心设计哲学体现在:

  • 解耦思想:将可变部分与不变部分分离,避免紧密耦合
  • 灵活扩展:允许运行时动态改变行为,而非编译时固化
  • 非侵入式:业务逻辑无需强制继承框架特定基类

1.2 Spring的设计演进

Spring框架对初始化机制的演进体现"组合优于继承"的逐步深化:

2、init-method调用的 源码 深度解读

2.1 初始化流程的核心模板方法


// AbstractAutowireCapableBeanFactory.java
protected Object initializeBean(String beanName, Object bean, 
                               RootBeanDefinition mbd) {
    
    // 1. 调用Aware方法(固定流程)
    invokeAwareMethods(beanName, bean);
    
    // 2. BeanPostProcessor前置处理(扩展点)
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
    }

    try {
        // 3. 🔥 核心初始化方法(模板方法)
        invokeInitMethods(beanName, wrappedBean, mbd);
    } catch (Throwable ex) {
        // ... 异常处理
    }
    
    // 4. BeanPostProcessor后置处理(扩展点)
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
    }

    return wrappedBean;
}

2.2 init-method调用的关键实现


// AbstractAutowireCapableBeanFactory.java
protected void invokeInitMethods(String beanName, Object bean, 
                                RootBeanDefinition mbd) throws Exception {
    
    // 1. 处理InitializingBean接口实现
    if (bean instanceof InitializingBean) {
        // 安全校验(避免多次初始化)
        if (System.getSecurityManager() != null) {
            // 特权执行(确保权限控制)
            AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                ((InitializingBean) bean).afterPropertiesSet();
                return null;
            }, getAccessControlContext());
        } else {
            // 常规执行
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }
    
    // 2. 🔥 组合优于继承的核心实现:init-method调用
    String initMethodName = (mbd != null) ? mbd.getInitMethodName() : null;
    if (initMethodName != null && !(bean instanceof InitializingBean 
                                   && "afterPropertiesSet".equals(initMethodName))) {
        
        // 方法查找(支持层级继承)
        Method initMethod = ClassUtils.getMethodIfAvailable(
            bean.getClass(), 
            initMethodName,
            new Class<?>[0] // 无参方法
        );
        
        if (initMethod == null) {
            // 方法不存在时的错误处理
            if (mbd.isEnforceInitMethod()) {
                throw new BeanDefinitionValidationException(...);
            }
        } else {
            // 🔥 反射调用用户定义方法
            ReflectionUtils.makeAccessible(initMethod);
            if (System.getSecurityManager() != null) {
                // 特权执行
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    ReflectionUtils.invokeMethod(initMethod, bean);
                    return null;
                }, getAccessControlContext());
            } else {
                // 常规执行
                ReflectionUtils.invokeMethod(initMethod, bean);
            }
        }
    }
}

3、组合优于继承的架构优势

3.1 核心设计优势对比

设计维度继承方式组合方式Spring实现分析
耦合度高(编译时耦合)低(运行时绑定)XML/注解配置解耦
扩展性受限(单继承)无限制支持多个init方法组合
灵活性固定动态可配支持条件初始化
测试性困难(需模拟父类)容易(直接测试方法)POJO方法易于单元测试
业务侵入性高(强制继承)零侵入保持POJO纯净性

3.2 解耦设计图解

4、init-method 在 工业场景 的 使用 展示

4.1 来一个 分布式系统的 初始化组件


public class DistributedSystemInitializer {
    private ZookeeperCoordinator zkCoordinator;
    private ConfigCenterConnector configConnector;
    
    // 🔥 组合方式初始化方法
    public void initializeDistributedSystem() {
        // 阶段1: 配置中心初始化
        initConfigCenter();
        
        // 阶段2: 服务注册中心初始化
        initServiceRegistry();
        
        // 阶段3: 分布式锁初始化
        initDistributedLock();
    }
    
    private void initConfigCenter() {
        configConnector.connect();
        configConnector.loadRemoteConfig();
        log.info("✅ Config center initialized");
    }
    
    private void initServiceRegistry() {
        zkCoordinator.start();
        zkCoordinator.registerCurrentService();
        log.info("✅ Service registry initialized");
    }
    
    private void initDistributedLock() {
        // ... 分布式锁初始化逻辑
        log.info("✅ Distributed lock initialized");
    }
}

4.2 多阶段初始化配置


<!-- 第一阶段:基础配置初始化 -->
<bean id="phase1" class="com.app.SystemInitializer"
      init-method="initPhase1_BasicConfig"/>

<!-- 第二阶段:数据库连接池初始化,依赖第一阶段 -->
<bean id="phase2" class="com.app.SystemInitializer"
      init-method="initPhase2_DatabasePool"
      depends-on="phase1"/>

<!-- 第三阶段:缓存预热,依赖第二阶段 -->
<bean id="phase3" class="com.app.SystemInitializer"
      init-method="initPhase3_CacheWarming"
      depends-on="phase2"/>

四:模版模式 在 Spring 框架bean生命周期中的使用

模版模式 的特点:模板方法保障算法骨架稳定,钩子方法开放可控扩展

  • 模版方法:父类实现,定义算法骨架并固定执行流程,确保核心逻辑不可变,子类仅能通过实现抽象方法填充具体步骤
  • ‌钩子方法‌:一般子类实现,提供可选扩展点,允许子类通过覆盖方法干预父类流程(如跳过步骤或修改条件),实现灵活定制

Spring 框架的bean生命周期中 7 个关键扩展点,就用到了 模版模式 ,下面分析下

我们知道Bean(单例非懒加载)的生命周期分为几个主干流程

1、 Bean 的实例化阶段

这个阶段是 Spring 创建 Bean 对象的时候。可以在这个阶段插入自己的逻辑,比如在创建对象之前做一些准备工作,或者修改对象的创建方式。

2、 Bean 的属性注入阶段

当 Spring 把依赖的属性值注入到 Bean 中时,可以通过扩展点来检查或修改这些属性值。比如,验证某个属性是否合法,或者动态地改变它的值。

3、 Bean 的初始化阶段

在 Bean 被完全创建并配置好之后,Spring 会调用初始化方法。这个阶段可以用来执行一些额外的操作,比如加载缓存、启动线程等。

4、 Bean 的销毁阶段

当 Bean 不再被需要、即将被销毁时,可以通过扩展点来清理资源,比如关闭数据库连接、释放内存等。

框架bean生命周期 四个主干流程 和 7个 关键扩展点

在 Spring 框架bean生命周期中 ,包括了 实例化、属性注入、初始化、销毁 四个主干流程。

在 实例化、属性注入、初始化、销毁 这几个主干流程上, 提供了 7 个关键扩展点,允许开发者在不修改 Spring 源码的情况下,定制化 Bean 的行为,这些扩展点的实现就是模版模式

下图绿色部分是spring中bean生命周期的扩展点,这些扩展点的实现大部分基于模版模式

image-20250612173343500

下面列举bean生命周期的7大扩展点

image-20250612173512184

Spring 的这些扩展点,就像是给开发者提供了一个“改装车间”。

分析一下 Spring 扩展点 用了 哪些设计模式 ?

这些扩展点的设计, 主要依赖于以下两种设计模式:

1、观察者模式(Observer Pattern)

观察者模式的核心思想是“订阅-通知”。

Spring 允许在某些关键事件发生时,注册一个“监听器”(Listener)。当事件触发时,Spring 会自动通知的监听器去执行特定的逻辑。

比如,在 Bean 初始化完成后, 让 应用程序 可以注册一个监听器来做一些后续处理。

2、 模板模式(Template Pattern)

模板模式的核心思想是“定义骨架,允许细节定制”。

Spring 提供了一些基础的流程代码(骨架),同时预留了一些可以自定义的地方(细节)。

比如,Spring 定义了 Bean 的生命周期流程,但在每个阶段都留出了扩展点,让 应用程序 可以插入自己的逻辑。

通过观察者模式和模板模式,在不改动Spring 框架核心代码的前提下,应用 可以 灵活地调整框架的行为,以适应各种复杂的需求。

模板模式 在 Spring 扩展点 的应用 demo:数据库连接预热

下面通过一个简洁实用的案例,演示如何在不修改 Spring 框架源码的前提下,使用模板模式扩展点增强数据库连接池初始化流程。

数据库连接预热 的 业务需求

在应用启动时,预加载数据库连接池中的连接,避免第一个请求到来时的连接延迟。

1、创建扩展点实现类



public class ConnectionPoolWarmer implements SmartLifecycle {
    private final DataSource dataSource;
    private boolean isRunning = false;
    
    public ConnectionPoolWarmer(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    // 实现模板方法扩展点
    @Override
    public void start() {
        if (!isRunning) {
            System.out.println("🔥 开始预热数据库连接池...");
            try {
                // 预创建5个连接
                for (int i = 0; i < 5; i++) {
                    Connection conn = dataSource.getConnection();
                    // 立即释放回连接池
                    conn.close();
                }
                System.out.println("✅ 连接池预热完成,5个连接就绪");
            } catch (SQLException e) {
                System.err.println("⚠️ 连接池预热失败: " + e.getMessage());
            }
            isRunning = true;
        }
    }

    // 模板方法:定义执行顺序(低优先级确保在连接池初始化后执行)
    @Override
    public int getPhase() {
        return Integer.MIN_VALUE + 1000; 
    }

    // 模板方法:标记组件状态
    @Override
    public boolean isRunning() {
        return isRunning;
    }
}

2、注册扩展点(使用Java配置)



@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        // 创建Hikari连接池(生产环境推荐)
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("secret");
        config.setMinimumIdle(5);  // 最小空闲连接数
        config.setMaximumPoolSize(20); // 最大连接数
        return new HikariDataSource(config);
    }
    
    // 关键:注册连接池预热器
    @Bean
    public ConnectionPoolWarmer connectionPoolWarmer(DataSource dataSource) {
        return new ConnectionPoolWarmer(dataSource);
    }
}

3、 使用示例 - 业务服务类



@RestController
public class OrderController {
    
    @Autowired
    private OrderRepository repo;
    
    // 不需要修改业务代码
    @GetMapping("/orders")
    public List<Order> getOrders() {
        return repo.findAll(); // 第一次请求无需等待连接创建
    }
}

4、 扩展效果验证


[main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080
[main] INFO  com.example.DemoApplication - Started DemoApplication in 2.459 seconds
🔥 开始预热数据库连接池...
✅ 连接池预热完成,5个连接就绪

扩展点原理解析

1 、Spring生命周期扩展点

这段 Mermaid 流程图简洁地展示了 Spring Bean(非懒加载单例)的生命周期关键步骤:

1、创建Bean (Instantiation)

  • Spring 通过构造函数创建 Bean 实例
  • 属于 Bean 生命周期的起点

2、 属性注入 (Dependency Injection)

  • Spring 容器将依赖项注入到 Bean 中
  • 包括字段注入、setter 注入等

3、 BeanPostProcessor.before

  • 在初始化前执行的后处理器钩子
  • 常用实现:@PostConstruct 注解处理
  • 可在此阶段修改 Bean 属性或状态

4、 初始化方法 (Initialization)

执行 Bean 的初始化方法

实现方式:

  • InitializingBean 接口的 afterPropertiesSet() 方法
  • 自定义的 init-method
  • @PostConstruct 注解方法

5、 BeanPostProcessor.after

  • 在初始化后执行的后处理器钩子
  • AOP 代理在此阶段创建
  • 可返回 Bean 的包装版本

6、 Lifecycle.start

  • 如果实现了 Lifecycle 接口
  • 容器启动时调用 start() 方法
  • 用于启动后台线程或定时任务

7、 应用就绪 (Application Ready)

  • Bean 完全初始化完成
  • 进入服务状态,可被应用程序使用
  • 此时 Bean 已准备好处理请求

生命周期特点

  • 线性流程:单例 Bean 按此顺序初始化

  • 关键扩展点:BeanPostProcessor 提供强大的扩展能力

  • AOP 切入点:AOP 代理在初始化后创建

  • 生命周期接口:Spring 提供了多种生命周期接口供定制

此流程图突出了 Spring Bean 生命周期中最核心且面试中最常问到的步骤,去除了冗余细节,便于快速理解和记忆容器管理 Bean 的核心机制。

2、Spring生命周期扩展点 模板模式关键设计


// 框架骨架代码(简化)
public abstract class AbstractApplicationContext {
    // 模板方法:应用启动流程
    public void refresh() {
        // ...
        finishRefresh(); // -> 触发Lifecycle.start()
    }
    
    protected void finishRefresh() {
        // 🔥 模板方法扩展点
        getLifecycleProcessor().onRefresh();
    }
}

// SmartLifecycle 接口定义
public interface SmartLifecycle {
    // 扩展点1:启动逻辑(由开发者实现)
    void start();
    
    // 扩展点2:执行顺序控制(钩子方法)
    int getPhase();
}

五:细粒度 详解 Spring 框架 有哪些扩展点?

由于 平台篇幅限制,这里 省了 10000 字

原始的内容,请参考 本文 的 原文 地址

本文 的 原文 地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值