1、Spring IOC 容器的设计
我们知道,在 Spring 中实现控制反转的是 IoC 容器,所以对于 IoC 来说,最重要的就是容器。因为容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。那么, 在 Spring 框架中是如何设计容器的呢?我们来看一下:Spring IoC 容器的设计主要是基于以下两个接口:
- 实现 BeanFactory 接口的简单容器
- 实现 ApplicationContext 接口的高级容器
这两个容器间的关系如下图:
通过上面的图片我们可以发现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 额外提供的功能有:
- 支持国际化(MessageSource)
- 统一的资源文件访问方式(ResourcePatternResolver)
- 提供在监听器中注册 bean 的事件(ApplicationEventPublisher)
- 同时加载多个配置文件
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 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 设计的关系,相关接口和实现类的图如下:
可以发现它的体系很庞大,下面简单介绍一下图片中左边重要的接口和类:
- 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 容器时 需要以下几个步骤:
- 创建 IOC 配置文件的 Resource 抽象资源,这个抽象资源包含了 BeanDefinition 的定义信息。
- 创建一个 BeanFactory,这里使用 DefaultListableBeanFactory。
- 创建一个载入 BeanDefinition 的读取器,这里使用 XmlBeanDefinitionReader 来载入 XML 文件形式的 BeanDefinition,通过一个回调配置给 BeanFactory。
- 从定义好的资源位置读取配置信息,具体的解析过程由 XmlBeanDefinitionReader 来完成,完成整个载入和注册 Bean 定义后,需要的 IOC 容器就建立起来了,这个时候就可以使用 IOC 容器了。
关于 DefaultListableBeanFactory 方式创建容器更加详细的介绍可以参考:https://blog.csdn.net/csj941227/article/details/85050632
4、BeanFactory 的详细介绍
BeanFactory 接口位于 IOC 容器设计的最底层,它提供了 Spring IOC 容器最基本的功能,给具体的 IOC 容器的实现提供了规范。为此,我们来看看该接口中到底提供了哪些功能和规范(也就是接口中的方法),BeanFactory 接口中的方法如下图所示:
可以看到这里定义的只是一系列的接口方法,通过这一系列的 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 和它的基础实现类的体系结构图,如下所示:
卧槽、卧槽(奈何自己没文化,出口只能卧槽),还是关了吧,这也太复杂了,看到这么复杂是不是就不想看了?别急,我们暂时只看最下面一排即可。可以看到 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 的区别是生成了一张图如下:
我们知道 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(楼主)还是一个菜鸟,我也正在每天积累,学习,慢慢走向秃顶的路上。