Small Spring IOC篇:应用上下文实现和Bean的扩展

一、前言

上一节我们解决了属性注入问题( Resource 注入),以及把 XML 解析过程解耦出来了。

事实上前面的 BeanFactory 就已经是一个完整的 IOC 容器了,因此后面我们要做的就是让这个IOC容器更好用,也就是扩展更多功能。

这一节我们要实现应用上下文 ApplicationContext ,它解决了我们前面需要自己手动创建 beanFactory 的痛点,因为日常开发自己创建 beanFactory 是很麻烦的,所以我们要封装。我们可以通过测试类来看看区别:

java 复制代码
    @Test
    public void test_xml() {
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2. 读取配置文件&注册Bean
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("classpath:spring.xml");

        // 3. 获取Bean对象调用方法
        UserService userService = beanFactory.getBean("userService", UserService.class);
        String result = userService.queryUserInfo();
        System.out.println("测试结果:" + result);
    }


    /**
     * 自动方式:上下文自己识别出 XML 里注册的两个扩展点并应用
     */
    @Test
    public void test_xml() {
        // 1.初始化 BeanFactory
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:springPostProcessor.xml");

        // 2. 获取Bean对象调用方法
        UserService userService = applicationContext.getBean("userService", UserService.class);
        String result = userService.queryUserInfo();
        System.out.println("测试结果:" + result);
    }

可以看到,applicationContext**( ClassPathXmlApplicationContext 是继承链顶端,拥有所有的子类方法,正常开发时我们一般使用多态,拿到一个 ApplicationContext 对象)**把创建 beanFactory 和读取配置、注册 bean ,全部都放在一起了,因此没看代码就知道这里一定要使用模板方法模式,事实也如此,ApplicationContext 有自己的抽象类 AbstractApplicationContext 来规定骨架。

然而不止于此,真实的 Spring 是对外提供了扩展点的,比如图中框出来的其实都是可扩展点:
小林coding 的Bean的生命周期图 这里主要要讲两个扩展点:

1.BFPP:beanFactoryPostProcesor这个主要是用于修改 BeanDefinition ,进而可以实例化出修改后的Bean。有点类似于反射,通过修改模板实现在后续创建出自己想要的 bean 。

2.BPP:beanPostProcesor这个主要是用于在 Bean 最后初始化时进行扩展,比如@PostStruct、AOP织入。

因此在写 ApplicationContext 时,我们会将扩展点包裹进去,在抽象流程类中进行骨架编排。

这里我给出整体依赖关系图,尤其注意我框着的这一条继承链,其中 ClassPathXmlApplicationContext 是继承链的顶端,继承了最多的方法和实现,因此我们最终在日常写代码时都是调用的这个。

二、ApplicationContext

1.应用上下文抽象类

我喜欢先定义骨架,然后再去看具体实现(子类对方法的实现或者接口的实现)。

java 复制代码
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    @Override
    public void refresh() throws BeansException {
        // 1. 创建 BeanFactory,并加载 BeanDefinition
        refreshBeanFactory();

        // 2. 获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
        registerBeanPostProcessors(beanFactory);

        // 5. 提前实例化单例Bean对象
        beanFactory.preInstantiateSingletons();
    }

    protected abstract void refreshBeanFactory() throws BeansException;

    protected abstract ConfigurableListableBeanFactory getBeanFactory();

    private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
        for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        }
    }

    private void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class);
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorMap.values()) {
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        }
    }

    // ========================================================================
    // 下面这几个方法是把 ApplicationContext(继承自 ListableBeanFactory)
    // 承诺的接口,转发给内部的 BeanFactory 去实现 ------ 上下文本身不干活,只做代理
    // ========================================================================

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        return getBeanFactory().getBeansOfType(type);
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return getBeanFactory().getBeanDefinitionNames();
    }

    @Override
    public Object getBean(String name) throws BeansException {
        return getBeanFactory().getBean(name);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return getBeanFactory().getBean(name, args);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return getBeanFactory().getBean(name, requiredType);
    }

}

这个抽象类中最核心的当然就是refresh方法了:

2.refresh方法

接下来我将对refresh方法一步一步进行剖析:

java 复制代码
    @Override
    public void refresh() throws BeansException {
        // 1. 创建 BeanFactory,并加载 BeanDefinition
        refreshBeanFactory();

        // 2. 获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
        registerBeanPostProcessors(beanFactory);

        // 5. 提前实例化单例Bean对象
        beanFactory.preInstantiateSingletons();
    }

(1)创建beanFactory

首先是创建 BeanFactory,这个步骤我们交给子类实现了:

java 复制代码
protected abstract void refreshBeanFactory() throws BeansException;

而这个子类依然是个抽象类,负责提供(create)Bean工厂,其中对于加载 BeanDefinition 的方法还要交由它的子类去实现:

java 复制代码
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext{

    private DefaultListableBeanFactory beanFactory;

    @Override
    protected void refreshBeanFactory() throws BeansException {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        loadBeanDefinitions(beanFactory);
        this.beanFactory = beanFactory;
    }

    private DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory();
    }

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory);

    @Override
    protected ConfigurableListableBeanFactory getBeanFactory() {
        return beanFactory;
    }

}

这个加载 BeanDefinition 的类还是一个抽象类,负责将xml文件解析并加载注册bean,加载和注册的过程实际在我们上一节的 XmlBeanDefinitionReader 中的doLoadBeanDefinitions()处理。

而xml 的具体路径的配置交由子类处理,其实就是把这里面对于 xml 文件的路径解耦出来。

java 复制代码
new ClassPathXmlApplicationContext("classpath:springPostProcessor.xml");
java 复制代码
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableApplicationContext{

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory, this);
        String[] configLocations = getConfigLocations();
        if (null != configLocations){
            beanDefinitionReader.loadBeanDefinitions(configLocations);
        }
    }

    protected abstract String[] getConfigLocations();
}

这是继承链最顶端的子类了,在这里负责最终接收框架使用者给的 xml 路径,把这个路径沿着继承链往上面传回。

这里解耦这么多次,看着很吓人,但是可以用一句话总结:

将 xml 路径从框架使用者透传回底层进行解析,解析后直接加载和注册BeanDefinition。

java 复制代码
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext  {

    private String[] configLocations;

    public ClassPathXmlApplicationContext() {
    }

    /**
     * 从 XML 中加载 BeanDefinition,并刷新上下文
     *
     * @param configLocations
     * @throws BeansException
     */
    public ClassPathXmlApplicationContext(String configLocations) throws BeansException {
        this(new String[]{configLocations});
    }

    /**
     * 从 XML 中加载 BeanDefinition,并刷新上下文
     * @param configLocations
     * @throws BeansException
     */
    public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
        this.configLocations = configLocations;
        refresh();
    }

    @Override
    protected String[] getConfigLocations() {
        return configLocations;
    }
}

不要害怕这种继承链,其实就是:"夺一下算一下",本身这些方法完全可以交由一个类实现,但是为了做到职责归类和配置解耦,这些方法的实现的一部分是不会由当前类自行解决的,而是会遗留给子类解决。

如果是业务场景下,这种东西可以不靠这个实现,我们可以通过AOP、责任链来实现,但是由于这是底层框架,AOP都得自己写,并且需要考虑性能问题和扩展性问题,因此最好的解耦方式就是通过继承链的模板方法模式不断抽象出方法,然后丢给子类去实现。

理解了这个意图就很容易理解 Spring 的设计思路了。

(2)获得beanfactory

这一步没什么好说的就是简简单单的把上一步创建好的beanfactory拿出来,作为后续方法的参数使用

java 复制代码
        // 2. 获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

(3)BFPP修改

这一步就是第一个扩展点,BeanFactoryPostProcessor,我们先回顾一下概念:

BFPP:beanFactoryPostProcesor 这个主要是用于修改 BeanDefinition ,进而可以实例化出修改后的Bean。有点类似于反射,通过修改模板实现在后续创建出自己想要的 bean 。

java 复制代码
 // 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
invokeBeanFactoryPostProcessors(beanFactory);

这是 AbstractApplicationContext 中自己实现的方法,作用是调用BFPP接口,修改 BeanDifinition。而这个接口的实现,我们自然是根据业务需求自己实现了。

java 复制代码
private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
        for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        }
    }

这里我们用反射来找到实现过 BFPP 接口的所有类,全部找出来封到一个 Map 中,具体的方法实现是在 DefaultListableBeanFactory 中的,也就是我们默认的 BeanFactory ,因为这里面封装了BeanDefinition的集合,直接在这个对象中去是实现方法复用率更高,有点类似于 DDD 的充血模型。

java 复制代码
    /**
     * 按类型返回容器中所有匹配的 Bean 实例
     */
    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        Map<String, T> result = new HashMap<>();
        beanDefinitionMap.forEach((beanName, beanDefinition) -> {
            Class beanClass = beanDefinition.getBeanClass();
            if (type.isAssignableFrom(beanClass)) {
                result.put(beanName, (T) getBean(beanName));
            }
        });
        return result;
    }

BFPP接口定义:

java 复制代码
public interface BeanFactoryPostProcessor {

    /**
     * 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制
     *
     * @param beanFactory
     * @throws BeansException
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

这里给一个BFPP的实现示例:

java 复制代码
/**
 * BeanFactoryPostProcessor 示例:在所有 BeanDefinition 加载完、Bean 实例化之前,
 * 往 userService 的定义里加一个 company 属性。
 */
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
        PropertyValues propertyValues = beanDefinition.getPropertyValues();

        propertyValues.addPropertyValue(new PropertyValue("company", "改为:字节跳动"));
    }

}

这里小小总结一下,因为看着又有点复杂了。

首先理清楚为啥要实现接口才能修改 BeanDefinition ?

这是因为我们需要 beanFactory 获取我们想修改的 BeanBeanDefinition。而且这个 beanFactory 还不能是自己随便 new 一个出来的,我们就是要那个已经把所有 BeanDefinition 都装好的 beanFactory。因此当 Spring 框架进行到 invokeBeanFactoryPostProcessors(beanFactory) 的时候,要把 "装好"的beanFactory 作为参数传进去,才会为框架使用者提供那个"装好"的 beanFactory 参数。
修改的究竟是什么?

答案是修改了就是原来在 beanFactory 的 Map 中的那个原来就有的对象,而且这个修改不是向 AOP 一样创造一个代理对象,就是在原有基础上直接修改,修改后的对象和修改前的是同一个对象。
所以可以小小总结一下:

BFPP 修改是通过将装好的 beanFactory 传递给框架使用者,然后直接执行使用者设计的修改方案来直接修改 beanDefinition。

(4)BPP

还是先回顾一下概念:

BPP:beanPostProcesor这个主要是用于在 Bean 最后初始化时进行扩展,比如@PostStruct、AOP织入。

老样子,先看看在 ApplicationContext 中 BPP 在干嘛?

java 复制代码
// 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
registerBeanPostProcessors(beanFactory);
java 复制代码
    private void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class);
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorMap.values()) {
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        }
    }

你会发现这只是在注册 BPP,和刚刚的 BFPP 不一样,这里只注册,并没有执行!

所以要搞清楚一点:BPP的执行是在实例化后,但是注册是在实例化前。(至少在这个版本)

来看看 BPP 接口定义,其实其中就两个方法,前置初始化和后置初始化。

java 复制代码
public interface BeanPostProcessor {

    /**
     * 在 Bean 对象执行初始化方法之前,执行此方法
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    /**
     * 在 Bean 对象执行初始化方法之后,执行此方法
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;


}

这里我给一个实现类的示例,这个是由框架使用者去写的,目的是修改已经实例化好了的 bean 对象。

java 复制代码
/**
 * BeanPostProcessor 示例:在 Bean 初始化前后做点事。
 * 这里在「初始化之前」把 userService 的 location 改掉。
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if ("userService".equals(beanName)) {
            UserService userService = (UserService) bean;
            userService.setLocation("改为:北京");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

在 AbstractAutowireCapableBeanFactory 中,我们对于属性注入就要加一个阶段了,也就是我们的初始化阶段。

java 复制代码
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            bean = createBeanInstance(beanDefinition, beanName, args);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
        // 1. 执行 BeanPostProcessor Before 处理
        Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);

        // 待完成内容:invokeInitMethods(beanName, wrappedBean, beanDefinition);
        invokeInitMethods(beanName, wrappedBean, beanDefinition);

        // 2. 执行 BeanPostProcessor After 处理
        //    注意这里要传 wrappedBean(Before 阶段的产物),不能传原始的 bean,
        //    否则 Before 阶段返回的包装对象会被丢掉。
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        return wrappedBean;
    }

    private void invokeInitMethods(String beanName, Object wrappedBean, BeanDefinition beanDefinition) {
        //目前是空的哦,下一节真正去实现
    }

(5)预实例化

这个目的很简单,还记得前面我们的单例工厂是有缓存机制的,正常情况下是先 getBean(),然后才去 createBean() 的,于是我们这里先全部 get 一遍,这样所有 bean 都会在单例工厂缓存中了,后续首次调用也是调用的缓存中的 bean 了。

java 复制代码
        // 5. 提前实例化单例Bean对象
        beanFactory.preInstantiateSingletons();
java 复制代码
/**
 * 集大成者:把三条扩展线合并到一起
 *
 *   ListableBeanFactory        能枚举 Bean
 *   AutowireCapableBeanFactory 能自动装配
 *   ConfigurableBeanFactory    能配置工厂
 *
 * 提供分析和修改 Bean 定义的能力。
 */
public interface ConfigurableListableBeanFactory
        extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {

    BeanDefinition getBeanDefinition(String beanName) throws BeansException;

    /**
     * 提前实例化所有单例 Bean
     */
    void preInstantiateSingletons() throws BeansException;

}
java 复制代码
    /**
     * 提前实例化所有单例 Bean。
     * 上下文 refresh() 的最后一步会调它,这样 Bean 在容器启动时就全部创建好。
     */
    @Override
    public void preInstantiateSingletons() throws BeansException {
        beanDefinitionMap.keySet().forEach(this::getBean);
    }

三、总结

回顾这一节,我们首先应该分清楚两个维度:

大的维度是ApplicationContext中的refresh,这里面装配了BF、执行BFPP的修改、注册BPP、最后实例化。

小的维度是 AbstractAutowireCapableBeanFactory ,这里面实例化了bean对象,然后装配属性,最后执行初始化。

这个小维度藏在大维度的最后一步,因此也可以将执行顺序看成大维度然后小维度,这也就是下面的图了。

相关推荐
Cosolar1 小时前
DeepInfer 完全教程:DeepSeek 内部推理框架原理、架构与部署实战
后端·架构·github
Pniubi1 小时前
ConcurrentHashMap拆分线程池难点再讲
java·jvm
golang学习记1 小时前
IDEA 的 Java 智能,现在跑到 Cursor 和 VS Code 里去了
java·ide·vscode·intellij-idea·claude code
喵个咪1 小时前
GoWind ShortPlay 拆解:Ent privacy/viewer 多租户隔离——从中间件到 SQL 的零信任链
后端·架构·go
全栈弄潮儿1 小时前
Python实战第11期:面向对象编程基础
后端·python·编程语言
用户69371750013841 小时前
当我说 Claude 搭配 GLM-5.3-Flash 太好用了,同事却提醒我:你可能正在失去对代码的掌控
android·后端·ios
喵个咪1 小时前
GoWind ShortPlay 拆解:不用模型的推荐闭环——埋点回流、三路召回与已看去重
后端·架构·go
喵个咪1 小时前
GoWind ShortPlay 深度拆解:三服务 BFF + Core,一条请求的完整工程链路
后端·架构·go
devilnumber1 小时前
springboot的保护机制怎么实现的,为何能做到服务不宕机
java·spring boot·后端