美文网首页SpringFrameworkSpringBoot极简教程 · Spring Boot
Spring(二)核心容器 - 简介 、BeanFactory、

Spring(二)核心容器 - 简介 、BeanFactory、

作者: 龙二丶 | 来源:发表于2020-01-20 08:19 被阅读0次

    前言

    在上篇文章中,和大家一起讨论了 Spring 的整体架构,其大致分为五个模块:核心容器、AOP、Web、Data 数据访问、Test模块。其中核心容器是 Spring 的核心部分,其它模块也都依赖于该容器。这里和就大家一起深入讨论 Spring 的容器,它的作用是什么、怎么实现的。

    1、容器简介

    容器顾名思义就是用来装东西的,装的是什么?装的是 Bean。

    Bean 是 Spring 的基本单位,在基于 Spring 的 Java EE 应用中,所有的组件都被当成 Bean 处理,包括数据源、Hibernate 的 SessionFactory、事务管理器等。在 Spring 中,Bean 是一个非常广义的概念,任何 Java 对象、Java 组件都被当成 Bean 处理。

    那容器仅仅是用来保存 Bean 这么简单么?不是。

    当我们需要使用某个 Bean 时,容器会自动帮我们创建,并在适当时销毁。还有一种情况,当某个 Bean 中需创建另一个 Bean 时,也就是 Bean 之间有依赖关系,这种依赖的 Bean 也是由容器自动创建。在外界有一个标准的名词,前者称呼为 IOC,也就是控制反转,后者称呼为 DI,也就是依赖注入。

    IOC/DI

    IOC (Inversion of Control) 控制反转:所谓控制反转,就是当我们需要某个 Bean 时,将 Bean 的名称告知容器,由容器去创建该 Bean,而不是我们手动 new 一个,这里 Bean 创建管理的控制权都交给了容器,所以这是一种控制权的反转。其通俗点讲就是需要什么东西让别人送过来,而不是自己去拿。

    DI (Dependency Injection) 依赖注入:就是指当 A Bean 里面需创建 B Bean 时,会在创建 A Bean 的时候,自动将依赖的 B Bean 注入进去,其 B Bean 是被动接受注入而不是自己主动去找。换句话说就是指 A Bean 不是从容器中查找它依赖的 B Bean,而是在容器创建 A Bean 候主动将它依赖的 B Bean 注入给它。

    IOC 和 DI 其实归根结底实现的功能是相同的,只是同样的功能站在不同的角度来阐述罢了,不过我们通常喜欢将这两个概念统称为 IOC。当然,在真实场景中,交由 Spring 容器创建的 Bean 泛指在应用程序中的表现层、业务层、持久层等各层对应的 Bean,如 Controller、Service 等;进行数据交互的模型,如 DTO、VO 等就不需交由 Spring 来创建。

    所以,容器本质上可以也可以看作是 Bean 工厂,该工厂管理 Bean 的生命周期,以及 Bean 之间的依赖关系。外界也将 Spring 容器称为 IOC 容器。当然,这里容器仅仅是 Spring 的抽象概念,代码中将其具象化为 BeanFactory 或 ApplicationContext,容器功能也由具象化的类进行处理。

    2、容器的结构

    容器的实现类并不是唯一的,Spring 框架提供了多个容器的实现,这些容器分为两套体系:一套是早期的 BeanFactory 体系;还有一套是现在常用的 ApplicationContext,也可称为应用上下文,它继承了 BeanFactory,它除了有 BeanFactory 的功能外
    ,还提供了其他服务,例如事务和 AOP 服务、国际化(il8n)的消息源以及应用程序事件处理等企业级的服务。

    说到这,就不得不说 Spring 的两种配置方式,在早期都是 XML 配置文件的方式,而现在使用的是注解配置的方式。BeanFactory 体系的容器一般用来处理 XML 配置文件的方式,而 ApplicationContext 体系则都可以处理。

    2.1 BeanFactory

    BeanFactory 是容器最基础的类,它定义了容器的基本功能规范:

    public interface BeanFactory {
    
        // 对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象,
        // 如果需要得到工厂本身,需要转义(FactoryBean 在后续会详细介绍)
        String FACTORY_BEAN_PREFIX = "&";
        
        // 根据 bean 的名字,获取在容器中 bean 实例
        Object getBean(String name) throws BeansException;
        
        //根据 bean 的名字和 Class 类型来得到 bean 实例,增加了类型安全验证机制。
        <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
        Object getBean(String name, Object... args) throws BeansException;
        <T> T getBean(Class<T> requiredType) throws BeansException;
        <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
        
        // 提供对 bean 的检索,看看是否在容器有这个名字的 bean
        boolean containsBean(String name);
        
        // 根据 bean 名字,判断这个 bean 是不是单例
        boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
        
        // 根据 bean 名字,判断这个 bean 是不是原型
        boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
        
        // 根据 bean 名字,判断是否与指定的类型匹配
        boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
        boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
        
        // 得到 bean 实例的 Class 类型
        Class<?> getType(String name) throws NoSuchBeanDefinitionException;
        
        // 得到bean 的别名,如果根据别名检索,那么其原名也会被检索出来
        String[] getAliases(String name);
    }
    

    在 BeanFactory 里只对容器的基本行为作了定义,其根本不关心你的 Bean 是如何定义怎样加载的。
    正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。而要知道工厂是如何产生对象的,我们就需要看具体的容器了,也就是 BeanFactory 的子类。

    BeanFactory 体系中常用的实现类有:

    • ListableBeanFactory:提供容器中 bean 迭代的功能。如返回所有 Bean 的名字、容器中 Bean 的数量等。
    • HierarchicalBeanFactory:提供父容器的访问功能,可通过 ConfigurableBeanFactory 的 setParentBeanFactory 方法设置父容器。
    • AutowireCapableBeanFactory:为 Spring 容器之外的 Bean ,也就是未交由 Spring 管理的 Bean ,提供依赖注入的功能。

    以上三个是 BeanFactory 的直系亲属,这个三个直系亲属下面又派生了两个复杂的容器:

    • ConfigurableBeanFactory:其继承了 HierarchicalBeanFactory 和 SingletonBeanRegistry 这两个接口,其提供了很多方法,如:定义类加载器、类型转化、属性编辑器、注册依赖 Bean 、销毁 bean 等,且该接口被大多数的容器继承、实现。
    • ConfigurableListableBeanFactory:这个接口继承了 ListableBeanFactory、 AutowireCapableBeanFactory、ConfigurableBeanFactory,自身主要提供用于分析和修改 bean 定义以及预先实例化单例 Bean 的方法。

    最后是核心容器:

    • DefaultListableBeanFactory:它实现了以上所有的接口,在 BeanFactory 体系中可以作为一个独立的容器使用。

    BeanFactory 大致的继承关系如下:

    BeanFactory 结构

    其实以前常用的容器是 XmlBeanFactory ,它是 DefaultListableBeanFactory 的实现类,现已被废除,原因还未找到,有知道的小伙伴,可在底下留言告知。

    但我们基本不单独使用 BeanFactory ,而是直接使用 ApplicationContext ,因为 ApplicationContext 包含了 BeanFactory。

    2.2 ApplicationContext

    上面说过 ApplicationContext 是 BeanFactory 子类,它不仅包含 BeanFactory 所有功能,还对其进行了扩展,而我们喜欢将 ApplicationContext 称为应用上下文,因为容器只是它的基本功能。

    public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
            MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    
        // 返回此应用程序上下文的唯一ID
        @Nullable
        String getId();
    
        // 返回此上下文所属的应用程序名称
        String getApplicationName();
    
        // 返回应用上下文具像化的类名
        String getDisplayName();
    
        // 返回第一次加载此上下文时的时间戳
        long getStartupDate();
    
        // 获取父级应用上下文
        @Nullable
        ApplicationContext getParent();
    
        // 将 AutowireCapableBeanFactory 接口暴露给外部使用
        AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
    }
    

    ApplicationContext 自身提供的方法非常简单,但它继承了六个接口,来扩展自身功能:

    • EnvironmentCapable:获取 Environment。
    • ListableBeanFactory、HierarchicalBeanFactory:这是 BeanFactory 体系接口,分别提供 Bean 迭代和访问父容器的功能。
    • MessageSource:支持国际化功能。
    • ApplicationEventPublisher:应用事件发布器,封装事件发布功能的接口。
    • ResourcePatternResolver:该接口继承至 ResourceLoader ,作用是加载多个 Resource。

    ApplicationContext 同样提供了非常多的实现类,其又可细分为两大类, ConfigurableApplicationContext 和 WebApplicationContext。

    2.2.1 ConfigurableApplicationContext

    该接口是比较重要的一个接口,几乎所有的应用上下文都实现了该接口。该接口在ApplicationContext的基础上提供了配置应用上下文的能力,此外提供了生命周期的控制能力。

    public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
    
        // 应用上下文配置时,这些符号用于分割多个配置路径
        String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
    
        // BeanFactory中,ConversionService类所对应的bean的名字。如果没有此类的实例的话吗,则使用默认的转换规则
        String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
    
        //LoadTimeWaver类所对应的Bean在容器中的名字。如果提供了该实例,上下文会使用临时的 ClassLoader ,这样,LoadTimeWaver就可以使用bean确切的类型了 
        String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";
    
        // Environment 类在容器中实例的名字
        String ENVIRONMENT_BEAN_NAME = "environment";
    
        // System 系统变量在容器中对应的Bean的名字
        String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";
    
        // System 环境变量在容器中对应的Bean的名字
        String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";
    
        // 设置容器的唯一ID
        void setId(String id);
    
        // 设置此容器的父容器
        void setParent(@Nullable ApplicationContext parent);
    
        // 设置容器的 Environment 变量
        void setEnvironment(ConfigurableEnvironment environment);
    
        // 以 ConfigurableEnvironment 的形式返回此容器的环境变量。以使用户更好的进行配置
        @Override
        ConfigurableEnvironment getEnvironment();
    
        // 此方法一般在读取应用上下文配置的时候调用,用以向此容器中增加BeanFactoryPostProcessor。增加的Processor会在容器refresh的时候使用。
        void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
    
        // 向容器增加一个 ApplicationListener,增加的 Listener 用于发布上下文事件,如 refresh 和 shutdown 等
        void addApplicationListener(ApplicationListener<?> listener);
    
        // 向容器中注入给定的 Protocol resolver
        void addProtocolResolver(ProtocolResolver resolver);
    
        // 这是初始化方法,因此如果调用此方法失败的情况下,要将其已经创建的 Bean 销毁。
        // 换句话说,调用此方法以后,要么所有的Bean都实例化好了,要么就一个都没有实例化
        void refresh() throws BeansException, IllegalStateException;
    
        // 向JVM注册一个回调函数,用以在JVM关闭时,销毁此应用上下文
        void registerShutdownHook();
    
        // 关闭此应用上下文,释放其所占有的所有资源和锁。并销毁其所有创建好的 singleton Beans
        @Override
        void close();
    
        // 检测此 FactoryBean 是否被启动过
        boolean isActive();
    
        // 返回此应用上下文的容器。
        // 千万不要使用此方法来对 BeanFactory 生成的 Bean 做后置处理,因为单例 Bean 在此之前已经生成。
        // 这种情况下应该使用 BeanFactoryPostProcessor 来在 Bean 生成之前对其进行处理
        ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
    }
    

    该接口下又有几个重要的实现类:

    • AbstractApplicationContext:这是个抽象类,仅实现了公共的上下文特性。这个抽象类使用了模板方法设计模式,需要具体的实现类去实现这些抽象的方法。
    • GenericApplicationContext:该类继承自 AbstractApplicationContext,是为通用目的设计的,它能加载各种配置文件,例如 xml,properties 等等。它的内部持有一个 DefaultListableBeanFactory 的实例,实现了 BeanDefinitionRegistry 接口,以便允许向其应用任何 bean 的定义的读取器。
    • AnnotationConfigApplicationContext:该类继承自 GenericApplicationContext ,提供了注解配置(例如:@Configuration、@Component等)和类路径扫描(scan方法)的支持。

    2.2.2 WebApplicationContext

    该接口是专门为 Web 应用准备的,其允许从相对于 Web 根目录的路径中装载配置文件完成初始化。

    public interface WebApplicationContext extends ApplicationContext {
    
        // 整个 Web 应用上下文是作为属性放置在 ServletContext 中的,该常量就是应用上下文在 ServletContext 属性列表中的 key
        String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    
        // 定义了三个作用域的名称
        String SCOPE_REQUEST = "request";
        String SCOPE_SESSION = "session";
        String SCOPE_APPLICATION = "application";
    
        // 在工厂中的 bean 名称
        String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
    
        // ServletContext 初始化参数名称
        String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
    
        // 在工厂中 ServletContext 属性值环境bean的名称
        String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
    
        // 用来获取 ServletContext 对象
        @Nullable
        ServletContext getServletContext();
    }
    

    该接口的核心实现类有:

    • ConfigurableWebApplicationContext:该接口同时继承了 WebApplicationContext 和 ConfigurableApplicationContext,提供了 Web 应用上下文的可配置的能力。
    • GenericWebApplicationContext:该类继承自 GenericApplicationContext,实现了 ConfigurableWebApplicationContext。
    • XmlWebApplicationContext:该上下文是使用 Xml 配置文件的方式,不过是在 Web 环境中使用的。
    • AnnotationConfigServletWebServerApplicationContext:该类是被 SpringBoot 扩展而来的,SpringBoot 使用的就是该上下文。

    2.3 差异对比

    从上面可以看出 BeanFactory 是 Sping 框架的基础接口,一般是面向 Spring 本身;而 ApplicationContext 是以 BeanFactory 为基础进行综合能力扩展,用于满足大型业务应用的创建, ApplicationContext 一般面向使用 Sping 框架的开发者。几乎所有的应用场合我们都是直接使用 ApplicationContet 而非底层的 BeanFactory。

    下表列出了BeanFactory 和 ApplicationContext 接口和实现所提供的功能:

    功能 / 特点 BeanFactory ApplicationContext
    Bean 实例化/装配
    BeanPostProcessor 自动注册 没有
    BeanFactoryPostProcessor 自动注册 没有
    MessageSource 便捷访问(针对i18n) 没有
    ApplicationEvent 发布 没有

    两者还有一个区别是:

    • ApplicationContext 在容器启动时,一次性创建了所有的 Bean。
    • BeanFactory 在容器启动时,并未创建 Bean,直到第一次访问某个 Bean 时才创建目标 Bean。

    3、ApplicationContext 准备启动

    在真实环境中,一般通过集成 SSM 或者 SpringBoot 来自动创建 ApplicationContext。

    先从 SSM 开始

    1、在 web.xml 配置监听器

    <!-- spring监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    2、容器启动时会调用 ContextLoaderListener 中的 contextInitialized 方法。

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
        
        ...
        
        @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }
        
        ...
    }
    

    3、调用父类的 initWebApplicationContext 方法,在该方法中创建、启动上下文。

    public class ContextLoader {
        
        ...
        
        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
            
            ...
    
            try {
                
                if (this.context == null) {
                
                    // 通过 createWebApplicationContext 方法创建上下文,默认创建 XmlWebApplicationContext
                    this.context = createWebApplicationContext(servletContext);
                }
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                    if (!cwac.isActive()) {
                        
                        ...
                        // 在该方法中调用上下文的 refresh 方法,refresh 就是启动上下文的入口
                        configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                ...
            }
            ...
        }
        ...
        
        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
            
            ... 
            
            wac.refresh();
        }
        
        ...
    }
    

    SpringBoot 启动 ApplicationContext

    1、从启动类开始

    @SpringBootApplication
    public class DiveInSpringBootApplication {
        public static void main(String[] args) {
            SpringApplication.run(DiveInSpringBootApplication.class, args);
        }
    }
    

    2、找到 SpringApplication 中,最后重载的 run 方法

    public ConfigurableApplicationContext run(String... args) {
        
        ...
        
        ConfigurableApplicationContext context = null;
        
        ...
        
        try {
            ...
            // 通过 createApplicationContext 方法创建上下文,根据 Web 环境不同创建的上下文也不同
            context = createApplicationContext();
        
            ...
            // 该方法用于启动上下文
            refreshContext(context);
            ...
        
        }
        catch (Throwable ex) {
            ...
        }
        
        context = createApplicationContext();
        
        ...
    }
    

    3、进入 refreshContext 方法,里面调用了 refresh 方法

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
    

    4、这里,最终也是调用 ApplicationContext 的 refresh 方法来启动上下文

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }
    

    注:这里主要讨论容器的创建和启动部分,所以省略了其他部分的代码。其中 SpringBoot 启动上下文在前几篇 《SpringBoot系列》文章有详细介绍,感兴趣的伙伴可自行查阅

    可以看到虽然 SSM 和 SpringBoot 的上下文对象不同,但最终都是调用上下文中的 refresh 方法来启动。该方法是 ApplicationContext 的核心,如 Bean 注册、注入、解析 XML 、解析注解等是从该方法开始,其内部实现大致如下:

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 1. 初始化 refresh 的上下文环境,就是记录下容器的启动时间、标记已启动状态、处理配置文件中的占位符
            prepareRefresh();
    
            // 2. 初始化 BeanFactory,加载并解析配置
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
    
            /* ---至此,已经完成了简单容器的所有功能,下面开始对简单容器进行增强--- */
    
            // 3. 对 BeanFactory 进行功能增强,如设置BeanFactory的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
            prepareBeanFactory(beanFactory);
    
            try {
                // 4. 后置处理 beanFactory,交由子类实现
                postProcessBeanFactory(beanFactory);
    
                // 5. 调用已注册的 BeanFactoryPostProcessor
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // 6. 注册 BeanPostProcessor,仅仅是注册,调用在getBean的时候
                registerBeanPostProcessors(beanFactory);
    
                // 7. 初始化国际化资源
                initMessageSource();
    
                // 8. 初始化事件广播器
                initApplicationEventMulticaster();
    
                // 9. 留给子类实现的模板方法
                onRefresh();
    
                // 10. 注册事件监听器
                registerListeners();
    
                // 11. 实例化所有非延迟加载的单例
                finishBeanFactoryInitialization(beanFactory);
    
                // 12. 完成刷新过程,发布应用事件
                finishRefresh();
                
            } catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
                }
                
                // 13.销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
                this.destroyBeans();
                
                // Reset 'active' flag.
                this.cancelRefresh(ex);
                // Propagate exception to caller.
                throw ex;
            } finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                this.resetCommonCaches();
            }
        }
    }
    

    接下来的文章,也是对 refresh 方法中的各部分进行详细讨论。

    4、总结

    最后来做个整体的总结。文章从 Spring 的整体架构开始讨论,整体分为五个模块:核心容器、AOP、Web、Data 数据访问、Test模块,而核心容器又是 Spring 的基础。容器的作用是什么?提供 IOC/DI 功能;怎么实现的?其核心是 BeanFactory 和 ApplicationContext ,一般使用 ApplicationContext ,其包含了 BeanFactory 的所有功能,并对其进行扩展。在 SSM 和 SpringBoot 中自动创建 ApplicationContext 并调用它的 refresh 方法进行启动,它的 refresh 就是实现容器一系列功能的入口。


    以上就是本章内容,如果文章中有错误或者需要补充的请及时提出,本人感激不尽。

    参考:

    https://www.cnblogs.com/09120912zhang/p/7746252.html
    https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#context-introduction
    https://www.jianshu.com/p/2854d8984dfc
    https://blog.csdn.net/baidu_36327010/article/details/87983262
    https://www.cnblogs.com/zhangfengxian/p/11192054.html
    https://www.cnblogs.com/sharpest/p/10885820.html

    相关文章

      网友评论

        本文标题:Spring(二)核心容器 - 简介 、BeanFactory、

        本文链接:https://www.haomeiwen.com/subject/huzdzctx.html