1、Spring IOC容器的设计

  • 2、BeanFactory和ApplicationContext的区别
  • 3、BeanFactory容器的设计原理
  • 4、BeanFactory的详细介绍
  • 5、ApplicationContext容器的设计原理
  • 6、ApplicationContext的详细介绍
  • 7、ApplicationContext容器扩展功能详解介绍
  •  


    1、Spring IOC容器的设计

    我们知道,在Spring中实现控制反转的是IoC容器,所以对于 IoC 来说,最重要的就是容器。因为容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。那么, 在Spring框架中是如何设计容器的呢?我们来看一下:Spring IoC 容器的设计主要是基于以下两个接口:

    • 实现BeanFactory接口的简单容器
    • 实现ApplicationContext接口的高级容器

    这两个容器间的关系如下图:

    image

    通过上面的图片我们可以发现ApplicationContext是BeanFactory的子接口。其中BeanFactory是Spring IoC容器的最底层接口,它只提供了IOC容器最基本的功能,给具体的IOC容器的实现提供了规范,所以我们称它为简单容器。它主要是负责配置、生产和管理bean,其内部定义了对单个bean的获取,对bean的作用域判断,获取bean类型,获取bean别名等功能。而ApplicationContext扩展(继承)了BeanFactory,所以ApplicationContext包含BeanFactory的所有功能,同时它又继承了MessageSource、ListableBeanFactory、ResourceLoader、ApplicationEventPublisher等接口,这样ApplicationContext为BeanFactory赋予了更高级的IOC容器特性,我们称它为高级容器。在实际应用中,一般不使用 BeanFactory,通常建议优先使用ApplicationContext(BeanFactory一般供代码内部使用)。

    注意:上面两个重要的类都是接口,既然是接口那总得有具体的实现类吧,那是由哪个类来具体实现IOC容器的呢?答:在BeanFactory子类中有一个DefaultListableBeanFactory类,它实现了包含基本Spirng IoC容器所具有的重要功能,我们开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用到DefaultListableBeanFactory类。在平时我们说BeanFactory提供了IOC容器最基本的功能和规范,但真正可以作为一个可以独立使用的IOC容器还是DefaultListableBeanFactory,因为它真正实现了BeanFactory接口中的方法。所以DefaultListableBeanFactory 是整个Spring IOC的始祖,在Spring中实际上把它当成默认的IoC容器来使用。但是暂时我们不深入了解,只需知道有这么个东西即可。

    2、BeanFactory和ApplicationContext的区别

    通过上面的介绍我们知道,BeanFactory和ApplicationContext是Spring IOC容器的两大核心接口,它们都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口,那么它们两者之间的区别在哪呢?下面我们来学习一下:

    ①、提供的功能不同:

    BeanFactory:是Spring里面最底层的接口,它只提供了IOC容器最基本的功能,给具体的IOC容器的实现提供了规范。包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系等。

    ApplicationContext:它作为BeanFactory的子接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。我们看一下ApplicationContext类结构:

    public interface ApplicationContext extends 
                                EnvironmentCapable,
                                ListableBeanFactory,
                                HierarchicalBeanFactory,
                                MessageSource,
                                ApplicationEventPublisher,
                                ResourcePatternResolver {
    
    }
    

    ApplicationContext额外提供的功能有:

    1. 支持国际化(MessageSource)
    2. 统一的资源文件访问方式(ResourcePatternResolver)
    3. 提供在监听器中注册bean的事件(ApplicationEventPublisher)
    4. 同时加载多个配置文件
    5. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层(HierarchicalBeanFactory)

    ②、 启动时的状态不同:

    BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

    ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。


    ③、BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

    ④、BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

    3、BeanFactory容器的设计原理

    我们知道,BeanFactory接口提供了使用IOC容器的基本规范,在这个基础上,Spring还提供了符合这个IOC容器接口的一系列容器的实现供开发人员使用,我们以DefaultListableBeanFactory的子类XmlBeanFactory的实现为例,来说明简单IOC容器的设计原理,下面的图为BeanFactory——>XmlBeanFactory设计的关系,相关接口和实现类的图如下:

    image

    可以发现它的体系很庞大,下面简单介绍一下图片中左边重要的接口和类:

    • BeanFactory接口:是Spring IOC容器的最底层接口,提供了容器的基本规范,如获取bean、是否包含bean、是否单例与原型、获取bean类型和bean别名的方法。
    • HierarchicalBeanFactory:提供父容器的访问功能,它内部定义了两个方法。
    • ListableBeanFactory:提供了列出工厂中所有的Bean的方法 定义了容器内Bean的枚举功能(枚举出来的Bean不会包含父容器)。
    • AutowireCapableBeanFactory:在BeanFactory基础上实现对已存在实例的管理,主要定义了集成其它框架的功能。一般应用开发者不会使用这个接口,所以像ApplicationContext这样的外观实现类不会实现这个接口,如果真想用可以通过ApplicationContext的getAutowireCapableBeanFactory接口获取。
    • ConfigurableBeanFactory:定义了BeanFactory的配置功能。
    • ConfigurableListableBeanFactory:继承了上述的所有接口,增加了其他功能:比如类加载器、类型转化、属性编辑器、BeanPostProcessor、作用域、bean定义、处理bean依赖关系、bean如何销毁等功能。
    • DefaultListableBeanFactory:实现上述BeanFactory接口中所有功能。它还可以注册BeanDefinition。
    • XmlBeanFactory :在Spring3.1之前使用,后面被标记为Deprecated,继承自DefaultListableBeanFactory,增加了对Xml文件解析的支持。

    通过上面的图片可以发现XmlBeanFactory是BeanFactory体系中的最底层的实现类,我们知道BeanFactory的实现主要是由DefaultListableBeanFactory类完成,而XmlBeanFactory又继承了DefaultListableBeanFactory类,所以说BeanFactory实现的最底层是XmlBeanFactory,这个类是Rod Johnson大佬在2001年就写下的代码,可见这个类应该是Spring的元老类了。由于那个时候没有使用注解,都是使用XML文件来配置Spring,所以XmlBeanFactory继承DefaultListableBeanFactory的目的就很明显,我们从XmlBeanFactory这个类的名字上就可以猜到,它是一个与XML相关的BeanFactory,没错,XmlBeanFactory在父类的基础上增加了对XML文件解析的支持,也就是说它是一个可以读取XML文件方式定义BeanDefinition的IOC容器。

    注意:这里说一下BeanDefinition:在Spring中BeanDefinition非常的重要,从字面意思就知道它跟Bean的定义有关。它是对 IOC容器中管理的对象依赖关系的数据抽象,是IOC容器实现控制反转功能的核心数据结构,控制反转功能都是围绕对这个BeanDefinition的处理来完成的,这些BeanDefinition就像是容器里裝的水一样,有了这些基本数据,容器才能够发挥作用。简单来说,BeanDefinition在Spring中是用来描述Bean对象的,它本身并不是一个Bean实例,而是包含了Bean实例的所有信息,比如类名、属性值、构造器参数、scope、依赖的bean、是否是单例类、是否是懒加载以及其它信息。其实就是将Bean实例定义的信息存储到这个BeanDefinition相应的属性中,后面Bean对象的创建是根据BeanDefinition中描述的信息来创建的,例如拿到这个BeanDefinition后,可以根据里面的类名、构造函数、构造函数参数,使用反射进行对象创建。也就是说 IOC容器可以有多个BeanDefinition,并且一个BeanDefinition对象对应一个<bean>标签中的信息。

    当然BeanDefinition的最终目的不只是用来存储Bean实例的所有信息,而是为了可以方便的进行修改属性值和其他元信息,比如通过BeanFactoryPostProcessor进行修改一些信息,然后在创建Bean对象的时候就可以结合原始信息和修改后的信息创建对象了。

    我们先来看一下使用XmlBeanFactory的方式创建容器,即使XmlBeanFactory已经过时了,但是有必要还是说一说。(以上一章橙汁和添加剂的栗子来举例)

    //创建XmlBeanFactory对象,并且传入Resource
    XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    //调用getBean方法获取实例对象
    OrangeJuice orangeJuice = (OrangeJuice) xmlBeanFactory.getBean("orangeJuice");
    orangeJuice.needOrangeJuice();
    

    可以发现这里的XmlBeanFactory构造函数中的参数是ClassPathResource类,而ClassPathResource类实现了Resource接口,这个Resource接口是定义资源文件的位置。在Spring框架中,如果我们需要读取Xml文件的信息,我们就需要知道这个文件在哪,也就是指定这个文件的来源。要让Spring知道这个来源,我们需要使用Resource类来完成。Resource类是Spring用来封装IO操作的类,通过Resoruce类实例化出一个具体的对象,比如ClasspathResource构造参数传入Xml文件名,然后将实例化好的Resource传给BeanFactory的构造参数来加载配置、管理对象,这样Spring就可以方便地定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程,也就是说Spring的配置文件的加载少不了Resource这个类。在XmlBeanFactory中对Xml定义文件的解析通过委托给 XmlBeanDefinitionReader 来完成,我们可以在XmlBeanFactory中看到。

    上面说了XmlBeanFactory已经淘汰不用了,那现在肯定有更好的方式来处理,我们先来分析一下XmlBeanFactory源码:

    @Deprecated
    public class XmlBeanFactory extends DefaultListableBeanFactory {
    
        private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
        
        public XmlBeanFactory(Resource resource) throws BeansException {
            this(resource, null);
        }   
        public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
            super(parentBeanFactory);
            this.reader.loadBeanDefinitions(resource);
        }
    }
    

    通过XmlBeanFactory的源码我们可以发现,在 XmlBeanFactory 中,初始化了一个 XmlBeanDefinitionReader对象,它的功能是读取Xml文件,将Bean的xml配置文件转换为多个BeanDefinition对象的工具类,一个BeanDefinition对象对应一个<bean>标签中的信息。XmlBeanFactory 中额外还定义了两个构造函数,可以看到第一个构造函数调用了第二个,所以重点看第二个,首先是调用了父类构造函数,然后执行loadBeanDefinition()方法,这个方法就是具体加载了BeanDefinition的操作,我们可以将这段代码抽取出来。所以下面我们我们以编程的方式使用DefaultListableBeanFactory,从中我们可以看到IOC容器使用的一些基本过程,对我们了解IOC容器的工作原理是非常有帮助的,因为这个编程式使用IOC容器过程,很清楚的揭示了在IOC容器实现中那些关键的类,可以看到他们是如何把IOC容器功能解耦的,又是如何结合在一起为IOC容器服务的,DefaultListableBeanFactory方式创建容器如下:

    //创建ClassPathResource对象,BeanDefinition的定义信息
    ClassPathResource resource = new ClassPathResource("applicationContext.xml");
    
    //创建一个DefaultListableBeanFactory对象,XmlBeanFactory 继承了这个类
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    
    /*创建一个载入IOC容器配置文件的读取器,这里使用XMLBeanFactory中使用的XmlBeanDefinitionReader读取器
    来载入XML文件形式的BeanDefinition,通过一个回到配置给BeanFactory*/
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    
    /*从定义好的资源位置读入配置信息,具体的解析过程有XmlBeanDefinitionReader来完成,
    完成整个载入和注册Bean定义后需要的IOC容器就建立起来了,这个时候就可以直接使用IOC容器了*/
    reader.loadBeanDefinitions(resource);
    
    //获取实例对象并调用方法
    OrangeJuice orangeJuice = (OrangeJuice) factory.getBean("orangeJuice");
    orangeJuice.needOrangeJuice();
    
    /*applicationContext.xml部分配置
    <bean id="additive" class="com.thr.Additive"></bean>
    
    <bean id="orangeJuice" class="com.thr.OrangeJuice">
    <property name="additive" ref="additive"></property>
    </bean>
    */
    

    总结:这样我们就可以通过Factory独享来使用DefaultListableBeanFactory这个IOC容器了,在使用IOC容器时 需要以下几个步骤:

    1. 创建IOC配置文件的Resource抽象资源,这个抽象资源包含了BeanDefinition的定义信息。
    2. 创建一个BeanFactory,这里使用DefaultListableBeanFactory。
    3. 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。
    4. 从定义好的资源位置读取配置信息,具体的解析过程由 XmlBeanDefinitionReader来完成,完成整个载入和注册Bean定义后,需要的IOC容器就建立起来了,这个时候就可以使用IOC容器了。

    关于DefaultListableBeanFactory方式创建容器更加详细的介绍可以参考:https://blog.csdn.net/csj941227/article/details/85050632

    4、BeanFactory的详细介绍

    BeanFactory 接口位于 IOC容器设计的最底层,它提供了 Spring IOC容器最基本的功能,给具体的IOC容器的实现提供了规范。为此,我们来看看该接口中到底提供了哪些功能和规范(也就是接口中的方法),BeanFactory 接口中的方法如下图所示:

    image

    可以看到这里定义的只是一系列的接口方法,通过这一系列的BeanFactory接口,可以使用不同的Bean的检索方法,很方便的从IOC容器中得到需要的Bean,从而忽略具体的IOC容器的实现,从这个角度看的话,这些检索方法代表的是最为基本的容器入口。其具体的方法有:5个获取实例的方法(getBean的重载方法);2个获取Bean的提供者;4个判断的方法(判断是否存在,是否为单例、原型,名称类型是否匹配);2个获取类型的方法和1个获取别名的方法。

    下面我们来看BeanFactory 具体的介绍:

    public interface BeanFactory {
    
        //用户使用容器时,可以使用转义符“&”来得到FactoryBean本身
        String FACTORY_BEAN_PREFIX = "&";
    
        //获取Bean
        Object getBean(String name) throws BeansException;
        <T> T getBean(String name, 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的提供者(对象工厂)
        <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
        <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    
        //判断是否包含指定名字的bean
        boolean containsBean(String name); 
        //获取指定名字的Bean是否是Singleton类型的Bean,对于Singleton属性,用户可以在BeanDefinition中指定
        boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
        //获取指定名字的Bean是否是Prototype类型的,与Singleton属性一样也可以在BeanDefinition中指定
        boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
        //指定名字的bean是否和指定的类型匹配
        boolean isTypeMatch(String name, ResolvableType typeToMatch);
        boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    
        //获取指定名字的Bean的Class类型
        Class<?> getType(String name) throws NoSuchBeanDefinitionException; 
    
        //获取指定名字的Bean的所有别名,这些别名是用户在BeanDefinition中定义的
        String[] getAliases(String name); 
    }
    

    正是由于BeanFactory是 Spring IoC 最底层的设计,其所有关于 Spring IoC 的容器将会遵守它所定义的方法。所以其内部定义的方法也极其重要,我们只有先搞清楚这个接口中的每一个方法,才能更好的理解IOC容器,下面我们对BeanFactory接口中的方法方法进行介绍。(同样以前面橙汁和添加剂的栗子来举例)如下:

    (1)、常量部分FACTORY_BEAN_PREFIX = "&"

    它的作用是如果在使用beanName获取Bean时,在BeanName前添加这个前缀(”&BeanName”), 那么使用这个BeanName获得的Bean实例是其所在FactoryBean的实例,也就是实现 FactoryBean 接口的那个类的Bean实例。

    关于BeanFactory和FactoryBean的区别可以参考:https://blog.csdn.net/wangbiao007/article/details/53183764


    (2)、getBean部分(重要):该方法表示获取bean实例

    ①、根据名字获取bean:getBean(String name)

     Object obj = (obj)factory.getBean("beanName");
    

    注意:这种方法不太安全,IDE 不会检查其安全性(关联性),所以我们必须强制转换类型。

    ②、根据类型获取bean:getBean(Class<T> requiredType)

    Object obj = factory.getBean(Bean.class);
    

    注意:要求在 Spring 中只配置了一个这种类型的实例,否则报错。(如果有多个那 Spring 就懵了,不知道该获取哪一个)

    ③、根据名字和类型获取bean(推荐):getBean(String name, Class<T> requiredType)

    Object obj = factory.getBean("beanName",Bean.class);
    

    这种方式解决上面两个方法的问题,所以推荐使用这个方法。

    ④、根据名称、类型和给定的构造函数参数或者工厂方法参数构造对象获取bean

    使用Bean名称寻找对应的Bean,使用给定的构造函数参数或者工厂方法参数构造对象并返回,会重写Bean定义中的默认参数。

    Object getBean(String name, Object... args) throws BeansException
    

    使用Bean类型寻找属于该类型的Bean,用给定的构造函数参数或工厂方法参数构造对象并返回,会重写Bean定义中的默认参数。

    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException
    

    注意:该两个方法只适用于prototype的Bean,默认作用域的Bean不能重写其参数。


    (3)、getBeanProvider部分:该方法表示获取bean的提供者(对象工厂)

    getBeanProvider方法用于获取指定bean的提供者,可以看到它返回的是一个ObjectProvider,其父级接口是ObjectFactory。首先来看一下ObjectFactory,它是一个对象的实例工厂,只有一个方法:

    T getObject() throws BeansException;
    

    调用这个方法返回的是一个对象的实例。此接口通常用于封装一个泛型工厂,在每次调用的时候返回一些目标对象新的实例。ObjectFactory和FactoryBean是类似的,只不过FactoryBean通常被定义为BeanFactory中的服务提供者(SPI)实例,而ObjectFactory通常是以API的形式提供给其他的bean。简单的来说,ObjectFactory一般是提供给开发者使用的,FactoryBean一般是提供给BeanFactory使用的。

    ObjectProvider继承ObjectFactory,特为注入点而设计,允许可选择性的编程和宽泛的非唯一性的处理。在Spring 5.1的时候,该接口从Iterable扩展,提供了对Stream的支持。该接口的方法如下:

    // 获取对象的实例,允许根据显式的指定构造器的参数去构造对象
    T getObject(Object... args) throws BeansException;
    // 获取对象的实例,如果不可用,则返回null
    T getIfAvailable() throws BeansException;
    T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException;
    void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException;
    // 获取对象的实例,如果不是唯一的或者没有首先的bean,则返回null
    T getIfUnique() throws BeansException;
    T getIfUnique(Supplier<T> defaultSupplier) throws BeansException;
    void ifUnique(Consumer<T> dependencyConsumer) throws BeansException;
    
    // 获取多个对象的实例
    Iterator<T> iterator();
    Stream<T> stream();
    Stream<T> orderedStream()
    

    这些接口是分为两类,

    • 一类是获取单个对象,getIfAvailable()方法用于获取可用的bean(没有则返回null),getIfUnique()方法用于获取唯一的bean(如果bean不是唯一的或者没有首选的bean返回null)。getIfAvailable(Supplier<T> defaultSupplier)getIfUnique(Supplier<T> defaultSupplier),如果没有获取到bean,则返回defaultSupplier提供的默认值,ifAvailable(Consumer<T> dependencyConsumer)ifUnique(Consumer<T> dependencyConsumer)提供了以函数式编程的方式去消费获取到的bean。
    • 另一类是获取多个对象,stream()方法返回连续的Stream,不保证bean的顺序(通常是bean的注册顺序)。orderedStream()方法返回连续的Stream,预先会根据工厂的公共排序比较器进行排序,一般是根据org.springframework.core.Ordered的约定进行排序。

    (4)、其它部分是一些工具性的方法

    • containsBean(String name):通过名字判断是否包含指定bean的定义 。
    • isSingleton(String name) isPrototype(String name):判断是单例和原型(多例)的方法。(注意:在默认情况下,isSingleton为 ture,而isPrototype为 false )。如果isSingleton为true,其意思是该 Bean 在容器中是作为一个唯一单例存在的。而isPrototype则相反,如果判断为真,意思是当你从容器中获取 Bean,容器就为你生成一个新的实例。
    • isTypeMatch:判断给定bean的名字是否和类型匹配 。
    • getType(String name):根据bean的名字来获取其类型的方法 (按 Java 类型匹配的方式 )。
    • getAliases(String name):根据bean的名字来获取其别名的方法。

    (5)、ResolvableType参数介绍

    或许你已经注意到了,有两个方法含有类型是ResolvableType的参数,那么ResolvableType是什么呢?假如说你要获取泛型类型的bean:MyBean,根据Class来获取,肯定是满足不了要求的,泛型在编译时会被擦除。使用ResolvableType就能满足此需求,代码如下:

    ResolvableType type = ResolvableType.forClassWithGenerics(MyType.class, TheType.class);
    ObjectProvider<MyType<TheType>> op = applicationContext.getBeanProvider(type);
    MyType<TheType> bean = op.getIfAvailable()
    

    简单的来说,ResolvableType是对Java java.lang.reflect.Type的封装,并且提供了一些访问该类型的其他信息的方法(例如父类, 泛型参数,该类)。从成员变量、方法参数、方法返回类型、类来构建ResolvableType的实例。

    5、ApplicationContext容器的设计原理

    我们知道ApplicationContext容器是扩展BeanFactory容器而来,在BeanFactory的基本让IoC容器功能更加丰富。如果说BeanFactory是Sping的心脏(提供了IOC容器的基本功能),那么ApplicationContext就是完整的身躯了(提供了更加高级的功能)。所以我们来看一下ApplicationContext和它的基础实现类的体系结构图,如下所示:

    image

    卧槽、卧槽(奈何自己没文化,出口只能卧槽imageimageimage),还是关了吧,这也太复杂了,看到这么复杂是不是就不想看了?别急,我们暂时只看最下面一排即可。可以看到ClassPathXmlApplicationContext这个类我们比较熟悉,因为在第二章Spring的入门案例中我们已经使用过ClassPathXmlApplicationContext这个类了。所以在ApplicationContext容器中,我们以常用的ClassPathXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理。使用classpath路径下的xml配置文件加载bean的方式如下:

    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml")

    下面对此代码进行分析,追踪源码来介绍它的设计原理如下所示:

    首先是new了ClassPathXmlApplicationContext对象,并且构造参数传入了一个xml文件,我们进入其构造方法(核心)如下:

        public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
            this(new String[] {configLocation}, true, null);
        }
    

    上面的参数configLocation表示的是Spring配置文件的路径,可以发现后面又调用了内部另一个构造方法如下:

        public ClassPathXmlApplicationContext(
                String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
                throws BeansException {
            // 1.初始化父类
            super(parent);
            // 2.设置本地的配置信息
            setConfigLocations(configLocations);
            // 3.完成Spring IOC容器的初始化
            if (refresh) {
                refresh();
            }
        }
    

    首先初始化了父类,就是一直到父类AbstractApplicationContext中,将ApplicationContext的环境属性设置给本类的环境属性,包括一些profile,系统属性等。

    然后设置本地的配置文件信息,这里调用其父类AbstractRefreshableConfigApplicationContext 的 setConfigLocations 方法,该方法主要处理ClassPathXmlApplicationContext传入的字符串中的占位符,即解析给定的路径数组(这里就一个),setConfigLocations 方法源码如下:

        public void setConfigLocations(@Nullable String... locations) {
            if (locations != null) {
                Assert.noNullElements(locations, "Config locations must not be null");
                this.configLocations = new String[locations.length];
                for (int i = 0; i < locations.length; i++) {
                    //循环取出每一个path参数,在此处就一个applicationContext.xml
                    this.configLocations[i] = resolvePath(locations[i]).trim();
                }
            }
            else {
                this.configLocations = null;
            }
        }
    

    setConfigLocations方法除了处理ClassPathXmlApplicationContext传入的字符串中的占位符之外,其实还有一个作用:创建环境对象ConfigurableEnvironment。详细可以参考:https://blog.csdn.net/boling_cavalry/article/details/80958832

    当本地配置文件解析完成之后,就可以准备实现容器的各个功能了。

    然后调用了refresh()方法,这个方法非常非常非常重要,它算是ApplicationContext容器最核心的部分了,因为这个refresh过程会牵涉IOC容器启动的一系列复杂操作,ApplicationContext的refresh()方法里面操作的不只是简单 IoC容器,而是高级容器的所有功能(包括 IoC),所以你说这个方法重不重要。而对于不同的高级容器的实现,其操作都是类似的(比如FileSystemXmlApplicationContext),因此将其具体的操作封装在父类 AbstractApplicationContext 中,在其子类中仅仅涉及到简单的调用而已。所以我们来看看AbstractApplicationContext类,可以看到refresh方法的源码如下(AbstractApplicationContext.refresh() 源码脉络):

    //AbstractApplicationContext.refresh()方法
    public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                //刷新上下文环境
                prepareRefresh();
    
                // Tell the subclass to refresh the internal bean factory.
                //这里是在子类中启动 refreshBeanFactory() 的地方,获得新的BeanFactory,解析XML、Java类,并加载BeanDefinition
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                //准备bean工厂,以便在此上下文中使用
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    //设置 beanFactory 的后置处理
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    //调用 BeanFactory 的后处理器,这些处理器是在Bean 定义中向容器注册的
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    //注册Bean的后处理器,在Bean创建过程中调用
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    //对上下文中的消息源进行初始化
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    //初始化上下文中的事件机制
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    //初始化其他特殊的Bean
                    onRefresh();
    
                    // Check for listener beans and register them.
                    //检查监听Bean并且将这些监听Bean向容器注册
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    //实例化所有的(non-lazy-init)单件
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    //发布容器事件,结束Refresh过程
                    finishRefresh();
                }
    
                catch (BeansException ex) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Exception encountered during context initialization - " +
                                "cancelling refresh attempt: " + ex);
                    }
    
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    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...
                    //重置Spring公共的缓存
                    resetCommonCaches();
                }
            }
        }
    

    对上面refresh方法中调用的各个方法详细的介绍:

    • prepareRefresh() :为刷新准备上下文,主要设置状态量(是否关闭,是否激活),记录启动时间,初始化属性资源占位符、校验必填属性是否配置及初始化用于存储早期应用事件的容器。
    • obtainFreshBeanFactory():主要用于获取一个新的BeanFactory,如果BeanFactory已存在,则将其销毁并重建,默认重建的BeanFactory为AbstractRefreshableApplicationContext;此外此方法委托其子类从XML中或基于注解的类中加载BeanDefinition。
    • prepareBeanFactory():配置BeanFactory使其具有一个上下文的标准特征,如上下文的类加载器、后处理程序(post-processors,如设置如总感知接口)。
    • postprocessBeanFactory():在应用上下文内部的BeanFactory初始化结束后对其进行修改,在所有的BeanDefinition已被加载但还没有实例化bean, 此刻可以注册一些特殊的BeanPostFactory,如web应用会注册ServletContextAwareProcessor等。
    • invokeBeanFactoryPostProcessors():调用注册在上下文中的BeanFactoryPostProcessor,如果有顺序则按顺序调用,并且一定再单列对象实例化之前调用。
    • registerBeanPostProcessors():实例化并注册BeanPostProcessor,如果有显式的顺序则按照顺序调用一定在所有bean实例化之前调用。
    • initMessageSource():初始化MessageSource,如果当前上下文没有定义则使用其父类的,如果BeanFactory中不能找到名称为messageSource中的bean, 则默认使用DelegatingMessageSource。
    • initApplicationEventMulticaster():初始化ApplicationEventMulticaster,如果上下文没有定义则默认使用SimpleApplicationEventMulticaster,此类主要用于广播ApplicationEvent。
    • onRefresh() :在一些特定的上下文子类中初始化特定的bean,如在Webapp的上下文中初始化主题资源。
    • registerListeners():添加实现了ApplicationListener的bean作为监听器,它不影响非bean的监听器;还会使用多播器发布早期的ApplicationEvent。
    • finishBeanFactoryInitialization():实例化所有非延迟加载的单例,完成BeanFactory的初始化工作。
    • finishRefresh():完成上下文的刷新工作,调用LifecycleProcessor的onFresh()及发布的ContextRefreshEvent事件。
    • resetCommonCaches():重置Spring公共的缓存,如:ReflectionUtils、ResolvableType、CachedIntrospectionResults的缓存CachedIntrospectionResults的缓存。

    上述各个方法的详细介绍可以参考:https://blog.csdn.net/boling_cavalry/article/details/81045637

    ApplicationContext的设计原理暂时就介绍到这里吧!!!下面来介绍一下ApplicationContext容器中常用的一些实现类。

    6、ApplicationContext的详细介绍

    对于ApplicationContext高级容器的详细介绍我们就不看它的的源码了,主要来介绍一下它的具体实现类,因为平时我们在开发中使用它的实现类比较多。ApplicationContext的中文意思为“应用上下文”,它继承自BeanFactory,给IOC容器提供更加高级的功能,所以我们称它为高级容器,ApplicationContext接口有以下常用的实现类,如下所示:

    实现类 描述
    ClassPathXmlApplicationContext 从系统类路径classpath下加载一个或多个xml配置文件,适用于xml配置的方式
    FileSystemXmlApplicationContext 从系统磁盘下加载一个或多个xml配置文件(必须有访问权限)
    XmlWebApplicationContext 从web应用下加载一个或多个xml配置文件,适用于web应用的xml配置方式
    AnnotationConfigApplicationContext 从Java注解的配置类中Spring的ApplicationContext容器。使用注解避免使用application.xml进行配置。相比XML配置,更加便捷
    AnnotationConfigWebApplicationContext 专门为web应用准备的用于读取注解创建容器的类

    下面详细介绍各个实现类的使用方式:

    (1)、ClassPathXmlApplicationContext:从系统类路径classpath下加载一个或多个xml配置文件,找到并装载完成ApplicationContext的实例化工作。例如:

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    

    (2)、FileSystemXmlApplicationContext:从系统磁盘下加载一个或多个xml配置文件(必须有访问权限)。也就是读取系统磁盘指定路径的xml文件。例如:

    ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");
    

    它与ClassPathXmlApplicationContext的区别在于读取Spring配置文件的方式,FileSystemXmlApplicationContext不在从类路径下读取配置文件,而是通过制定参数从系统磁盘读取,前提是有访问权限。


    (3)、XmlWebApplicationContext:从web应用下加载一个或多个xml配置文件,适用于web应用的xml配置方式。

    在Java项目中提供ClassPathXmlApplicationContext类手工实例化ApplicationContext容器通常是不二之选,但是对于Web项目就不行了,Web项目的启动是由相应的Web服务器负责的,因此,在Web项目中ApplicationContext容器的实例化工作最好交由Web服务器来完成。Spring为此提供了以下两种方式:

    • org.springframework.web.context.ContextLoaderListener
    • org.springframework.web.context.ContexLoaderServlet(此方法目前以废弃)

    ContextLoaderListener方式只适用于Servlet2.4及以上规范的Servlet,并且需要Web环境。我们需要在web.xml中添加如下配置:

        <!--从类路径下加载Spring配置文件,classpath特指类路径下加载-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath:applicationContext.xml
            </param-value>
        </context-param>
        <!--Listener的方式启动spring容器-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    

    当Spring容器启动后就可以在项目中获取对应的实例了。例如:

    @WebServlet("/MyServlet")
    public class MyServlet {
    
            public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                //创建XmlWebApplicationContext对象,但这时并没有初始化容器
                XmlWebApplicationContext context = new XmlWebApplicationContext();
                // 指定配置文件路径
                context.setConfigLocation("application.xml");
                // 需要指定ServletContext对象
                context.setServletContext(request.getServletContext());
                // 初始化容器
                context.refresh();
                //获取实例
                Additive additive = (Additive) context.getBean("additive");
                additive.addAdditive();
    
            }
    }
    

    (4)、AnnotationConfigApplicationContext:从Java注解的配置类中加载Spring的ApplicationContext容器。使用注解避免使用application.xml进行配置。相比XML配置,更加便捷。

    创建一个AppConfig配置类(OrangeJuice和Additive类参考上一章内容)。例如:

    @Configuration
    public class AppConfig {
    
        @Bean(name = "orangeJuice")
        public OrangeJuice orangeJuice(){
            OrangeJuice orangeJuice = new OrangeJuice();
            return orangeJuice;
        }
    
        @Bean(name = "additive")
        public Additive additive(){
            Additive additive = new Additive();
            return additive;
        }
    }
    

    注意:@Configuration和@Bean注解的介绍和理解

    • @Configuration可理解为用spring的时候xml里面的标签。
    • @Bean可理解为用spring的时候xml里面的标签,默认name为方法名。

    使用AnnotationConfigApplicationContext获取Spring容器实例。代码如下:

       //创建AnnotationConfigApplicationContext对象,此时并没有初始化容器
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
       //将AppConfig中的配置注册至容器中
       context.register(AppConfig.class);
       // 初始化容器
       context.refresh();
    
       //获取实例对象
       OrangeJuice orangeJuice = (OrangeJuice) context.getBean("orangeJuice");
       Additive additive = (Additive) context.getBean("additive");
       orangeJuice.setAdditive(additive);
       orangeJuice.needOrangeJuice();
    

    (5)、AnnotationConfigWebApplicationContext:专门为web应用准备的用于读取注解创建容器的类。

    如果是Web项目使用@Configuration的java类提供配置信息的配置 web.xml 配置修改如下:

        <!--通过指定context参数,让Spring使用AnnotationConfigWebApplicationContext启动容器
        而非XmlWebApplicationContext。默认没配置时是使用XmlWebApplicationContext-->
        <context-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </context-param>
        <!--指定标注了@Configuration的类,多个可以用逗号分隔-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.thr.AppConfig</param-value>
        </context-param>
        <!--监听器将根据上面的配置使用AnnotationConfigWebApplicationContext
        根据contextConfigLocation指定的配置类启动Spring容器-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    

    7、ApplicationContext容器扩展功能详解介绍

    前面在介绍BeanFactory和ApplicationContext的区别是生成了一张图如下:

    image

    我们知道ApplicationContext容器正是因为继承了红框中的这些接口,使用才让ApplicationContext容器有了更加高级的功能。所以下面来详细介绍红框中各个接口:

    (1)、ListableBeanFactory——可将Bean逐一列出的工厂

    ListableBeanFactory接口能够列出工厂中所有的bean,下面是该接口的源码:

    /**
     * ListableBeanFactory源码介绍
     */
    public interface ListableBeanFactory extends BeanFactory {
        //判断是否包含给定名字的bean的定义
        boolean containsBeanDefinition(String beanName);
        //获取工厂中bean的定义的数量
        int getBeanDefinitionCount();
        //获取工厂中所有定义了的bean的名字(包括子类)
        String[] getBeanDefinitionNames();
    
        //获取指定类型的bean的名字(includeNonSingletons为false表示只取单例Bean,true则不是;
        //allowEagerInit为true表示立刻加载,false表示延迟加载。 注意:FactoryBeans都是立刻加载的。)
        String[] getBeanNamesForType(ResolvableType type);
        String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit);
        String[] getBeanNamesForType(@Nullable Class<?> type);
        String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
    
        //根据指定的类型来获取所有的bean名和bean对象的Map集合(包括子类)
        <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
        <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
                throws BeansException;
    
        //根据注解类型,获取所有有这个注解的bean名称
        String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
        //根据注解类型,获取所有有这个注解的bean名和bean对象的Map集合
        Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
        //根据bean名和注解类型查找所有指定的注解(会考虑接口和父类中的注解)
        @Nullable
        <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
                throws NoSuchBeanDefinitionException;
    }
    

    上面的这些方法都不考虑祖先工厂中的bean,只会考虑在当前工厂中定义的bean。


    (2)、HierarchicalBeanFactory——分层的Bean工厂

    HierarchicalBeanFactory接口定义了BeanFactory之间的分层结构,ConfigurableBeanFactory中的setParentBeanFactory方法能设置父级的BeanFactory,下面列出了HierarchicalBeanFactory中定义的方法:

    /**
     * HierarchicalBeanFactory源码介绍
     */
    public interface HierarchicalBeanFactory extends BeanFactory {
    
        //获取本Bean工厂的父工厂
        @Nullable
        BeanFactory getParentBeanFactory();
    
        //本地的工厂是否包含指定名字的bean
        boolean containsLocalBean(String name);
    }
    

    这两个方法都比较直接明了,getParentBeanFactory方法用于获取父级BeanFactory。containsLocalBean用于判断本地的工厂是否包含指定的bean,忽略在祖先工厂中定义的bean。


    (3)、MessageSource——消息的国际化

    在前面也提到过MessageSource,它主要用于消息的国际化,下面是该接口的源码:

    // 获取消息
    String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
    String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    

    以上的三个方法都是用于获取消息的,第一个方法提供了默认消息,第二个接口如果没有获取到指定的消息会抛出异常。第三个接口中的MessageSourceResolvable参数是对代码、参数值、默认值的一个封装。


    (4)、ApplicationEventPublisher

    ApplicationEventPublisher接口封装了事件发布功能,提供Spring中事件的机制。接口中的方法定义如下:

    // 发布事件
    void publishEvent(ApplicationEvent event);
    void publishEvent(Object event);
    

    第一个方法用于发布特定于应用程序事件。第二个方法能发布任意的事件,如果事件不是ApplicationEvent,那么会被包裹成PayloadApplicationEvent事件。


    (5)、EnvironmentCapable

    EnvironmentCapable提供了访问Environment的能力,该接口只有一个方法:

    Environment getEnvironment();
    

    Environment表示当前正在运行的应用的环境变量,它分为两个部分:profiles和properties。它的父级接口PropertyResolver提供了property的访问能力。


    (6)、ResourceLoader和ResourcePatternResolver

    首先来看一下ResourceLoader,听名字就知道该接口是用来加载资源的策略接口(例如类路径或者文件系统中的资源)。该接口中的源码如下:

    /**
     * ResourceLoader源码介绍
     */
    public interface ResourceLoader {
    
        //用于从类路径加载的伪URL前缀:" classpath:"。
        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
        //根据指定的位置获取资源
        Resource getResource(String location);
        //获取该资源加载器所使用的类加载器
        ClassLoader getClassLoader();
    }
    

    该接口只有简单明了的两个方法,一个是用来获取指定位置的资源,一个用于获取资源加载器所使用的类加载器。

    Resource是从实际类型的底层资源(例如文件、类路径资源)进行抽象的资源描述符。再看下Resource的源码:

    /**
     * Resource源码介绍
     */
    public interface Resource extends InputStreamSource {
    
        boolean exists(); // 资源实际上是否存在
    
        boolean isReadable(); // 资源是否可读
    
        boolean isOpen(); // 检查资源是否为打开的流
    
        boolean isFile(); // 资源是否为文件系统上的一个文件
    
        URL getURL() throws IOException; // 获取url
    
        URI getURI() throws IOException; // 获取URI
    
        File getFile() throws IOException; // 获取文件
    
        ReadableByteChannel readableChannel() throws IOException; // 获取ReadableByteChannel
    
        long contentLength() throws IOException; // 资源的内容的长度
    
        long lastModified() throws IOException; // 资源的最后修改时间
    
        // 相对于当前的资源创建一个新的资源
        Resource createRelative(String relativePath) throws IOException;
    
        String getFilename(); // 获取资源的文件名
    
        String getDescription(); // 获取资源的描述信息
    }
    

    Resource的父级接口为InputStreamSource,可以简单的理解为InputStream的来源,其内部只有一个方法,如下:

    // 获取输入流
    InputStream getInputStream() throws IOException; 
    

    接下来在来看一下ResourcePatternResolver,该接口用于解析一个位置模式(例如Ant风格的路径模式),该接口也只有一个方法,如下:

    // 将给定的位置模式解析成资源对象
    Resource[] getResources(String locationPattern) throws IOException;
    

    至此BeanFactory和ApplicationContext容器的设计已经全部介绍完了。如果哪里有问题欢迎大家多多讨论,留言,毕竟LZ(楼主)还是一个菜鸟,我也正在每天积累,学习,慢慢走向秃顶的路上。


    参考资料:

    1. 《JavaEE入门实战》
    2. https://www.cnblogs.com/zhangfengxian/p/11086695.html
    3. https://www.cnblogs.com/ChenD/p/10235579.html
    4. https://www.cnblogs.com/lichangyun/p/10698951.html

    点赞(0)

    评论列表 共有 0 条评论

    暂无评论
    立即
    投稿
    发表
    评论
    返回
    顶部