Spring Context 你真的懂了吗
   一些代码   0 评论   1001 浏览

Spring Context 你真的懂了吗

   一些代码   0 评论   1001 浏览

介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。

context 是什么

我们经常在编程中见到 context 这个单词,当然每个人有每个人的理解,它被理解为:上下文、容器等等。我想说的是,context 理解为上下文最为合适。为什么呢?我以一个在计算机系统的例子来解释一下。

在计算机系统中,进程执行时有进程上下文,如果进程在执行的过程中遇到了中断,CPU 会从用户态切换为内核态(当然这个过程用户进程是感知不到的,由硬件来实现的),此时进程处于的进程上下文会被切换到中断上下文中,从而可以根据中断号去执行相应的中断程序。

通过上面这个例子我们可以发现,进程在执行程序(不管是用户程序,还是内核中的中断程序)时,都会依赖一个上下文,这个上下文由多种数据结构组成,可以提供我们运行时需要的一些数据和保存运行时的一些数据。那其实 context 就可以理解对一个程序运行时所需要的一些数据结构的抽象表达呗。

抽象是个好东西,可以更方便的表达一些东西,更好的设计系统,但大家要想进步也不能停留在抽象层面,要去探索它的真正含义,真正对应的实体。有时间和大家聊一聊抽象应该怎么去理解。

spring context 是什么

回到 spring 中,spring 的 ioc 容器也是程序呀,那它的执行也肯定需要依赖一个上下文。所以大家应该理解 spring context 的意思了吧。

那 spring context 既然是 spring 的上下文,按照我们上面的说法上下文会对应数据结构,那 spring context 的数据结构是什么呢?

换句话说,spring context 究竟包括什么?接下来我就把这个抽象的概念给大家对应到实打实的数据结构上。

spring context 包括什么

主要包括:

spring context 的生命周期

下面大家可以结合代码这段代码去理解 spring context 的生命周期。

public static void main(String[] args) {
    // 初始化和启动
    AnnotationConfigApplicationContext acaContext = new AnnotationConfigApplicationContext(AppConfig.class);
    // 运行
    acaContext.getBean(ServiceA.class);
    // 关闭/销毁
    acaContext.close();
}

初始化和启动

我们平时常说的spring 启动其实就是调用 AbstractApplicationContext#refresh 完成 spring context 的初始化和启动过程。spring context 初始化从开始到最后结束以及启动,这整个过程都在 refresh 这个方法中。refresh 方法刚开始做的是一些 spring context 的准备工作,也就是 spring context 的初始化,比如:创建 BeanFactory、注册 BeanFactoryPostProcessor 等,只有等这些准备工作做好以后才去开始 spring context 的启动。

与现实生活联系一下,你可以把初始化理解为准备原料(对应到编程中就是创建好一些数据结构,并为这些数据结构填充点数据进去),等准备了你才能去真正造玩偶、造东西呀(对应到编程中就是执行算法)。在编程中数据结构与算法是分不开的也是这个道理呀,它们相互依赖并没有严格的界限划分。

运行

spring context 启动后可以提供它的服务的这段时间。

关闭/销毁

不需要用 spring context ,关闭它时,其实对应到代码上就是 acaContext.close();


实现spring-context

前言

  对tiny-spring项目的详细解读,聚焦spring-context的基本实现,对应着(seventh~ninth)-stage这三个构建过程。

引入ResourceLoader

  Spring提供的ApplicationContextBeanFactory的基础之上,添加了几个重要特性:资源加载、事件派发和国际化。不仅如此,相较于BeanFactory只能手动注册服务,ApplicationContext可以自动发现和注册服务,比如BeanPostProcessorBeanFactoryPostProcessor,并且ApplicationContext在创建时就会一次性加载所有non lazy initJavaBean,可以说ApplicationContextBeanFactory提高了一个台阶,更易于客户端程序的使用。

  让我们先来看看增强的资源加载功能。在第一篇中我们说到Spring提供了一个对资源的统一抽象——Resource,并由此衍生出多个代表不同来源的Resource实现。然而,在基础BeanFactory的使用过程中,具体使用什么类型的Resource是需要客户端程序手动指定的:

Resource resource = new ClassPathResource("test/config.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);

比如这里,我们选择使用ClassPathResource是通过硬编码实现的,有没有什么办法可以让Spring自动识别资源的类型呢?为了解决这个问题,Spring引入了一个策略接口ResourceLoader。实际上,Spring还提供了一个ResourceLoader的子接口ResourcePatternResolver,它可以一次性加载多个资源。

// 命令行输入 git checkout seventh-stage,在io包中可以看到对ResourceLoader的定义

/**
 * 策略接口,用来加载资源。
 * Discussion: 可以根据location的不同表现形式,
 * 返回不同的Resource,故而称为策略接口。
 */
public interface ResourceLoader {
    /**
     * 加载一个资源,可以是ClassPathResource/FileSystemResource等等。
     */
    Resource getResource(String location);

    /**
     * 返回ResourceLoader在加载资源时使用的类加载器。
     */
    ClassLoader getClassLoader();
}

有了ResourceLoader,我们只需要提供资源的地址,至于它到底是什么类型的资源Spring会帮我们做判断,而不再需要我们手动指定。DefaultResourceLoader是一个ResourceLoader的实现,它根据资源地址中的协议类型来判断具体的资源类型,代码比较简单,这里就不多说了。

引入事件机制

  BeanFactory为其持有的各种JavaBean提供了一套统一的生命周期管理(init-methodInitializingBean,etc),但客户端程序却没有多少手段可以干涉到BeanFactory本身。为此ApplicationContext提供了一套事件机制,用以向客户端程序报告ApplicationContext的进程,当然也支持派发自定义事件。实际上ApplicationContext还实现了Lifecycle接口,客户端程序可以依此来开启或关闭ApplicationContext,不过在我们的实现中并没有提供这一层抽象。

  ApplicationContext中的事件处理是由ApplicationEvent类和ApplicationListener接口来提供的,两者分别基于标准的java.util.EventObjectjava.util.EventListener,事件的派发则是通过ApplicationEventPublisher接口。Spring中事件派发机制使用观察者模式来实现。

/**
 * 应用程序事件派发器,封装了事件的派发逻辑,一般作为
 * ApplicationContext的父接口使用。
 */
public interface ApplicationEventPublisher {
    /**
     * 派发一个ApplicationEvent,并通知所有已注册的ApplicationListener。
     */
    void publishEvent(ApplicationEvent event);
}

为了支持ApplicationEventPublisher的工作,Spring又提供了ApplicationEventMulticaster接口,它支持对ApplicationListener的加入、移除管理,还可以将收到的ApplicationEvent派发到已注册的ApplicationListener,可以说ApplicationEventMulticaster就是ApplicationEventPublisher的一个代理。

/**
 * 应用程序事件多播器,管理着一组ApplicationListener,并将
 * ApplicationEvent派发给它们。
 *
 * ApplicationEventPublisher就是利用ApplicationEventMulticaster
 * 来将事件派发给监听器。
 */
public interface ApplicationEventMulticaster {
    /**
     * 添加一个ApplicationListener来接收ApplicationEvent。
     */
    void addApplicationListener(ApplicationListener listener);

    /**
     * 移除一个ApplicationListener。
     */
    void removeApplicationListener(ApplicationListener listener);

    /**
     * 移除所有已注册的ApplicationListener。
     */
    void removeAllListeners();

    /**
     * 将给定的ApplicationEvent多播到适当的ApplicationListener(s)。
     */
    void multicastEvent(ApplicationEvent event);
}

DefaultApplicationEventMulticaster实现了ApplicationEventMulticaster接口,非常简单的一个类,各位同学可以自行翻阅。

引入ApplicationContext

  如果抛开国际化不谈,讲到这里ApplicationContext的定义也就呼之欲出了:

/**
 * 在BeanFactory之上集成许多易用的功能,更加方便客户端的使用,比如资源加载、事件派发等等。
 */
public interface ApplicationContext extends ResourceLoader, ListableBeanFactory, ApplicationEventPublisher {
    /**
     * 返回ApplicationContext的名称
     */
    String getDisplayName();

    /**
     * 返回ApplicationContext的启动时间
     */
    long getStartupDate();
}

上一篇提到的BeanFactory的设计思路类似,ApplicationContext也采用了分层的设计思想,其中一个比较重要的子接口是ConfigurableApplicationContext

/**
 * 类似于BeanFactory设计,ApplicationContext也只是一个功能最小的接口,
 * ConfigurableApplicationContext在此基础之上扩充了它的功能。
 */
public interface ConfigurableApplicationContext extends ApplicationContext {
    /**
     * 添加一个BeanFactory后置处理器。
     */
    void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor);

    /**
     * 添加一个ApplicationListener用来接收ApplicationContext的开启、关闭、刷新等事件。
     */
    void addApplicationListener(ApplicationListener listener);

    /**
     * 刷新ApplicationContext。
     */
    void refresh() throws BeansException, IllegalStateException;

    /**
     * 关闭当前ApplicationContext。
     */
    void close();

    /**
     * 返回当前ApplicationContext内部组合的BeanFactory。
     */
    ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}

实现ApplicationContext

  AbstractApplicationContext是Spring提供的一个模板类,它实现了ConfigurableApplicationContext的大部分基础功能。具体的,AbstractApplicationContext选择继承DefaultResourceLoader从而获得了资源加载的能力,同时通过ConfigurableApplicationContext.getFactory()返回的ConfigurableListableBeanFactory满足了对ListableBeanFactory的需求。这样的设计,使得AbstractApplicationContext成功的将BeanFactory的创建工作留给了子类。

  前面说过,ApplicationContext在启动时会一次性初始化所有non lazy initJavaBean,并且可以自动发现和注册服务,这又是怎么做到的呢?这一切的核心在于refresh()方法的实现,我们来具体看一下:

// 命令行输入 git checkout eighth-stage

@Override
public void refresh() throws BeansException, IllegalStateException {
    // 记录启动时间
    startupDate = System.currentTimeMillis();
    // 刷新内部BeanFactory
    ConfigurableListableBeanFactory beanFactory = refreshBeanFactory();
    // 为BeanFactory进行必要的准备工作
    prepareBeanFactory(beanFactory);
    try {
    // 进行额外的后置处理
    postProcessBeanFactory(beanFactory);
    // 执行BeanFactoryPostProcessor的回调
    invokeBeanFactoryPostProcessors(beanFactory);
    // 注册所有BeanPostProcessor
    registerBeanPostProcessors(beanFactory);
    // 初始化事件多播器
    initApplicationEventMulticaster();
    // 准备完成,正在刷新
    onRefresh();
    // 注册所有ApplicationListener
    registerApplicationListeners();
    // 完成BeanFactory的初始化流程
    finishBeanFactoryInitialization(beanFactory);
    // 完成刷新
    finishRefresh();
    } catch (BeansException e) {
    // 若失败,就清理资源
    beanFactory.destroySingletons();
    // 抛出这个异常给调用者
    throw e;
    }
}

  其中,refreshBeanFactory()是一个模板方法,显然我们应该在这里重新创建BeanFactory并加载配置文件,又因为refresh方法可以多次调用,所以我们也应该清理一下原来BeanFactory持有的资源(主要是缓存的singleton bean)。同时为了给子类留下进一步的定制空间,又定义了钩子方法prepareBeanFactory(...)postProcessBeanFactory(...)。提一下与ApplicationContext有关的几个Aware接口(ResourceLoaderAwareApplicationContextAwareApplicationEventPublisherAware),它们由名为ApplicationContextAwareProcessorBeanPostProcessor来负责处理,这个BeanPostProcessor就是在prepareBeanFactory(...)中注册的。

public class ApplicationContextAwareProcessor implements BeanPostProcessor {

    private final ApplicationContext applicationContext;

    public ApplicationContextAwareProcessor(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }

        return bean;
    }

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

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 添加Bean后置处理器
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
}

到这里,可以说BeanFactory已经完全准备完毕,是时候为BeanFactoryPostProcessor执行回调了。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    // 1. 处理直接注册的BeanFactoryPostProcessor
    for (BeanFactoryPostProcessor processor : getBeanFactoryPostProcessors()) {
        processor.postProcessBeanFactory(beanFactory);
    }
    // 2. 处理在配置文件中配置的BeanFactoryPostProcessor
    // 2.1 获取类型为BeanFactoryPostProcessor的所有beanName
    String[] beanNames = beanFactory.getBeanDefinitionNames(BeanFactoryPostProcessor.class);
    for (String beanName : beanNames) {
        // 2.2 在调用之前确保BeanFactoryPostProcessor得到初始化
        BeanFactoryPostProcessor processor = getBean(beanName, BeanFactoryPostProcessor.class);
        // 2.3 调用BeanFactoryPostProcessor的相关方法
        processor.postProcessBeanFactory(beanFactory);
    }
}

  这里分为两部分,首先是为直接注册的BeanFactoryPostProcessor执行回调,然后再去配置文件中寻找BeanFactoryPostProcessor,为它们执行回调,也就是所谓的自动发现服务。registerBeanPostProcessors(...)同理,它在配置文件寻找BeanPostProcessor,并将寻找到的BeanPostProcessor注册进BeanFactory

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // ApplicationContext表面上只是一个ListableBeanFactory,
    // 并不具备ConfigurableBeanFactory.addBeanPostProcessor(...)的能力。
    // 对ApplicationContext来说,配置文件中配置的BeanPostProcessor就是所有的了
    String[] beanNames = beanFactory.getBeanDefinitionNames(BeanPostProcessor.class);
    if (beanNames.length > 0) {
        for (String beanName : beanNames) {
            // 实例化所有BeanPostProcessor
            BeanPostProcessor processor = getBean(beanName, BeanPostProcessor.class);
            // 注册进beanFactory
            beanFactory.addBeanPostProcessor(processor);
        }
    }
}

  注意这里beanFactory.getBeanDefinitionNames(...)是通过查询BeanFactory持有的BeanDefinition(s)来判断,并不会导致任何bean的实例化。换句话说,执行完registerBeanPostProcessors(...),除了BeanFactoryPostProcessorBeanPostProcessor被实例化了之外,还没有其他bean被实例化。 NOTE: Spring还额外提供了Ordered接口来调整BeanFactoryPostProcessorBeanPostProcessor的调用顺序,tiny-spring并没有做这些。

  接下来的initApplicationEventMulticaster()创建了ApplicationEventMulticaster,它被用来实现ApplicationEventPublisher

@Override
public void publishEvent(ApplicationEvent event) {
    eventMulticaster.multicastEvent(event);
}

现在只要根据事件找到事件对应的监听器,把它交给ApplicationEventMulticaster管理,一个完整的观察者模式就实现了。在这里Observable对应ApplicationEventObserver对应ApplicationListener,事件发布则由ApplicationEventMulticaster来处理。onRefresh也是一个钩子方法,默认只是一个空的实现。

protected void registerApplicationListeners() {
    // 1. 重新注册所有手动注册上去的ApplicationListener
    manuallyRegisteredListeners.forEach(eventMulticaster::addApplicationListener);
    // 2. 注册配置文件中定义的ApplicationListener
    Collection<Object> listeners = getBeansOfType(ApplicationListener.class,true,false).values();
    for (Object listener : listeners) {
        eventMulticaster.addApplicationListener((ApplicationListener) listener);
    }
}

剩下的finishBeanFactoryInitialization(...)实现了ApplicationContext在启动时一次性初始化所有non lazy initJavaBean的功能:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    beanFactory.preInstantiateSingletons();
}

直接调用ConfigurableListableBeanFactory.preInstantiateSingletons(),简洁明了。finishRefresh()意味着刷新结束,因此它发出了ContextRefreshedEvent事件通知上一步注册的所有ApplicationListener

protected void finishRefresh() {
    publishEvent(new ContextRefreshedEvent(this));
}

  除了可以刷新,ApplicationContext还可以在不需要时被关闭:

@Override
public void close() {
    System.out.println("正在关闭" + getDisplayName());
    // 销毁所有缓存的singleton bean
    getBeanFactory().destroySingletons();
    // 发出ContextClosedEvent
    publishEvent(new ContextClosedEvent(this));
}

ApplicationContext在被关闭时会销毁所有的singleton bean,并发出ContextClosedEvent通知所有的ApplicationListener

  AbstractRefreshableApplicationContextAbstractApplicationContext的子类,它实现了BeanFactory的创建和刷新,并将配置文件的加载留给了子类。

@Override
protected ConfigurableListableBeanFactory refreshBeanFactory() {
    // 刷新时如果BeanFactory已经存在,首先要进行资源的清理
    if (beanFactory != null) {
        beanFactory.destroySingletons();
        beanFactory = null;
    }
    // 清理完毕重新创建BeanFactory并加载配置文件
    try {
        DefaultListableBeanFactory listableBeanFactory = createBeanFactory();
        loadBeanDefinitions(listableBeanFactory);
        beanFactory = listableBeanFactory;
        return listableBeanFactory;
    } catch (Exception e) {
        throw new ApplicationContextException("刷新BeanFactory失败", e);
    }
}

/**
 * 创建BeanFactory,可以由子类进一步定制。
 */
protected DefaultListableBeanFactory createBeanFactory() {
    return new DefaultListableBeanFactory();
}

/// MARK - Template method

/**
 * 将加载BeanDefinition的功能交由子类去实现,
 * 子类根据资源的类型,运用不同的加载方式,从而派生出不同的ApplicationContext。
 */
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException;

AbstractXMLApplicationContext在此基础之上实现了对配置文件的加载但屏蔽了它的来源,ClassPathXMLApplicationContext交代了配置文件的来源,从而成为一个真正可用的ApplicationContext

BeanFactoryPostProcessor示例

  在上一篇中,我们提到PropertyEditorRegistrar,使用它可以向BeanFactory中注册自定义的PropertyEditor,现在让我们来实现它。在了解了ApplicationContext的实现之后,我们知道在为BeanFactoryPostProcessor执行回调的时候,所有的BeanDefinition已经加载完毕,但是还没有任何bean得到了初始化,这是一个非常好的时机去更新BeanDefinitionCustomEditorConfigurer就利用了这个时机,先注册PropertyEditorBeanFactory中,后续bean的初始化过程就能用上自定义的PropertyEditor

// 命令行输入 git checkout ninth-stage
public class CustomEditorConfigurer implements BeanFactoryPostProcessor {

    // PropertyEditor注册器
    private PropertyEditorRegistrar[] propertyEditorRegistrars;

    public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
        this.propertyEditorRegistrars = propertyEditorRegistrars;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (propertyEditorRegistrars != null) {
            for (PropertyEditorRegistrar registrar : propertyEditorRegistrars) {
                beanFactory.addPropertyEditorRegistrar(registrar);
            }
        }
    }
}

为了让PropertyEditorRegistrarBeanFactory产生关联,我们需要修改一下ConfigurableBeanFactory的定义,增加对PropertyEditorRegistrar的管理:

/**
 * 添加一个PropertyEditorRegistrar。
 */
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);

AbstractBeanFactory中当然也就需要一个集合去保存PropertyEditorRegistrar:

// 保存所有的PropertyEditorRegistrar
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new HashSet<>();

@Override
public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
    propertyEditorRegistrars.add(registrar);
}

上一篇中也说到属性注入最后是交给了BeanWrapper去处理,BeanWrapper因而需要同步BeanFactory中的PropertyEditor。因此我们需要修改一下AbstractAutowireCapableBeanFactory中的同步过程,将与PropertyEditorRegistrar关联的PropertyEditor也同步过去。

/**
 * 将BeanFactory持有的PropertyEditor同步到BeanWrapper
 */
private void registerPropertyEditors(BeanWrapper wrapper) {
    // 1. 注册PropertyEditorRegistrar中的
    for (PropertyEditorRegistrar registrar : getPropertyEditorRegistrars()) {
        registrar.registerCustomEditors(wrapper);
    }
    // 2. 同步BeanFactory持有的
    Map<Class<?>, PropertyEditor> customEditors = getCustomEditors();
    Set<Class<?>> keys = customEditors.keySet();
    for (Class<?> type : keys) {
        wrapper.registerCustomEditor(type, customEditors.get(type));
    }
}

本文由 RawChen 发表, 最后编辑时间为:2021-11-13 13:19
如果你觉得我的文章不错,不妨鼓励我继续写作。

发表评论
选择表情
Top