一 什么是IOC
IOC(Inversion of Contorl)控制反转:所谓控制反转,就是把我们原先代码中需要实现的对象创建,反转给容器来实现,必然的我们需要创建一个容器,同样需要一种描述让容器知道需要创建对象与对象之间的关系,这个描述具体的表现就是我们可配置的文件。
DI(Dependency Injection)依赖注入:就是对象是被动接受依赖类而不是自己主动去寻找,换而言之就是对象不是从容器中查找它的依赖类而是在容器实例化的时候主动将它的依赖类注入给它。
我们从宏观的设计视角来俯瞰这个架构考虑,如果我们是IOC的设计者,我们该怎么解决如下的问题:
对象和对象的关系怎么表示?
可以用xml,properties文件等语义化配置文件表示。
描述对象关系的文件放在哪里?
可能是classpath,filesystem,或者是url网络资源,servletContext等等。
有了配置文件,我们还需要对配置文件进行解析。
不同的配置文件对对象描述不一样,如标准的,自定义声明的,如何统一?在内部需要有一个统一的关于对象的定义,所以外部的描述都必须转化成统一的描述定义。
如何对不同的配置文件进行解析?需要对不同的配置文件语法,采取不同的解析器。
二 Spring IOC体系架构
2.1 Beanfactory
Spring Bean 的创建是典型的工厂模式,这一系列的Bean工厂,就就是IOC容器为开发人员管理对象之间依赖关系提供了很多便利和基础服务,在Spring’中有许多IOC容器的实现供用户选择和使用,其相互关系如下:
最基础的IOC容器接口BeanFactory:
ApplicationContext是Spring容器提供的一个高级IOC容器,它能够提供IOC容器的基本功能之外,还为用户提供了如下的附加功能:
1 支持信息源,可以实现国际化。(实现MessageSource接口)
2 访问资源 (实现ResourcePatternResolver接口)
3 支持应用事件(实现了ApplicationEventpublisher接口)
2.2 BeanDefinition
SpringIOC容器管理我们定义的各种Bean对象及其相互关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下:
IOC容器的初始化包括BeanDefinition的Resource定位,载入,注册这三个基本过程。 我们以ApplicationContext为例讲解,ApplicationContext系列的容器是我们最常使用的,web项目中的XmlApplicationContext就是属于这个继承体系还有ClasspathXmlApplicationContext。其继承图如下:
下面分别看下俩种IOC容器创建的过程
2.3.1 XmlBeanFactory
通过XmlBeanFactory源码可以发现
调用的全过程还原,定位,载入,注册
然后再调用父类abstractRefreshableConfigApplicationContext的
setCongifLocaltions(configLocaltions)方法来设置Bean定义资源文件的定位路径
通过追踪FileSystemXmlApplicationContext的继承体系,发现其父类的父类
AbstractApplicationContext中初始化IOC容器所做的主要源码如下:
ClasspathResource res=new ClasspathResource(“a.xml,b.xml,c.cml…..”)
多个资源文件路径之间可以使用,;/t/n 分割
到这边,Spring IOC容器在初始化将配置的Bean定义资源文件定位为Spring封装的Resource。
AbstractApplicationContext 的refresh函数载入Bean定义过程:
Spring IOC容器对bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法。它的作用是:在创建IOC容器前,如果容器已经存在,则把已有的容器销毁和关闭,以保证在refresh之后使用新建立起来的IOC容器。Refresh的作用类似于IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。
FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh函数启动整个IOC容器对于Bean定义的载入过程。 、
// 容器初始化的过程,读入Bean定义资源,并解析注册
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()方法,Bean定义资源文件的载入从子类的refreshBeanFactory()方法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 为BeanFactory注册BeanPost事件处理器.
// BeanPostProcessor是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.
// 为事件传播器注册事件监听器.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化所有剩余的单例Bean.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
// 销毁以创建的单态Bean
destroyBeans();
// Reset 'active' flag.
// 取消refresh操作,重置容器的同步标识.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
Refresh方法主要为IOC容器的生命周期管理提供了条件,Spring IOC容器载入Bean定义资源文件从其子类容器中的refreshBeanFactory方法启动,所以整个refresh中的
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这句以后代码的都是注册容器的信息源和生命周期,载入过程就是从这句代码启动的。
refresh的作用是:在创建IOC容器前,如果容器已经存在,则把已有的容器销毁和关闭,以保证在refresh之后使用新建立起来的IOC容器。Refresh的作用类似于IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。
AbstractApplicationContext的obtainFreshBeanFactory方法调用子容器的refreshBeanFactory方法,启动容器载入Bean定义资源文件的过程,源码如下:
这边的loadBeanDefinitions同样是抽象方法,容器真正调用的是他的子类AbstractXmlApplicationContext中的实现,源码如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取在IoC容器初始化过程中设置的资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源
//加载多个指定位置的Bean定义资源文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
//将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源
//加载单个指定位置的Bean定义资源文件
Resource resource = resourceLoader.getResource(location);
//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
资源加载器获取要读入的资源:
XmlBeanDefinitionReader通过调用其父类defaultResourceLoader的getResource方法获取加载资源。源码如下:
在我们Bean定义的resource得到之后继续回到XmlBeanDefinitionReader的loadBeanDefiniyions(Resource …)方法看到代表Bean文件资源定义后的载入过程
我们看下这个doLoadBeanDefinitions源码最核心的就是获取文件流开始读取过程,在Spring源码中带do开头的就是具体干活的。
我们看下这个doLoadBeanDefinitions源码