Spring IoC容器概述
什么是控制反转?
Martin Fowler提出了“哪些方面的控制被反转了?”这个问题,结论是:依赖对象的获得被反转了。 即,依赖关系的管理由具体对象交给了框架或IoC容器,解耦代码降低系统复杂性的同时提高了代码的可测试性。
IoC系列容器的设计及实现(BeanFactory和ApplicationContext)
在Spring IoC容器的设计中,我们可以看到 2 个主要的容器系列:一个是实现
BeanFactory的简单容器系列,这系列容器只实现了容器的基本功能;另一个是ApplicationContext应用上下文,在简单容器的基础上,增加了许多面向框架的特性。
IoC容器的接口设计图
IoC容器接口设计主线
第一条接口设计主线是,从
BeanFactory到HierarchicalCapableBeanFactory,再到ConfigurableBeanFactory是一条主要的BeanFactory设计路径。在这条接口设计路径中,BeanFactory接口定义了基本的IoC容器的规范。
1)在这个接口定义中,包括了getBean()这样的IoC容器的基本方法;
2)HierarchicalCapableBeanFactory接口在继承了BeanFactory的基本接口之后,增加了getParentBeanFactory()的接口功能,使BeanFactory具备了双亲IoC容器的管理功能;
3)ConfigurableBeanFactory接口中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲IoC容器,通过addBeanPostProcessor()配置Bean后置处理器,等等。
第二条接口设计主线是,已
ApplicationContext应用上下文接口为核心的接口设计。这里设计的主要接口设计有,BeanFactory->ListableBeanFactory->ApplicationContext->WebApplicationContext/ConfigurableApplicationContext。我们常用的应用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的实现。在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了getBeanDefinitionNames()接口方法。
================== BeanFactory接口 ==================
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingletion(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException;
Class getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
可以看到,通过这一系列Bean的检索方法,可以很方便地从容器中获得需要的Bean,从而忽略具体的IoC容器的实现。从这个角度来看,这些检索方法代表的是最为基本的容器入口。
BeanFactory容器的设计原理
XmlBeanFactory类继承层次图如下:
作为一个简单IoC容器系列最底层实现的
XmlBeanFactory,与ApplicationContext上下文系列容器相比,有一个非常明显的特点:它只提供最基本的IoC容器的功能。
DefaultListableBeanFactory实际包含了基本IoC容器所具有的重要功能,在Spring中,实际是把其作为一个默认的功能完整的IoC容器来使用的。XmlBeanFactory是在DefaultListableBeanFactory基础上,增加了XML读取的功能(XmlBeanDefinitionReader)。
================== XmlBeanFactory的实现 ==================
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);
}
}
尽管我们实际工程应用中使用IoC容器很少使用这种原始的方式,但是了解这个基本过程,对我们了解IoC容器的工作原理大有裨益。因为这个编程式使用容器的过程,很清楚的揭示了在IoC容器实现中那些关键的类(比如Resource、DefaultListableBeanFactory和BeanDefinitionReader)之间的相互关系。例如,它们之间是如何把IoC容器的功能解耦的,又是如何结合在一起为IoC容器服务的,等等。
================== 编程式使用IoC容器 ==================
public class Codec {
public static void main(String[] args) {
ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
}
}
在使用IoC容器时,需要如下几个步骤:
1)创建IoC配置文件的抽象资源,这个抽象资源包含了BeanDefiniton的定义信息;
2)创建一个BeanFactory,这里使用DefaultListableBeanFactory;
3)创建一个载入BeanDefiniton的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefiniton,通过一个回调配置给BeanFactory;
4)从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义后,需要的IoC容器就建立起来了。
这个时候就可以直接使用IoC容器了。
ApplicationContext的应用场景
1、支持不同的数据源。ApplicationContext扩展了
MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务;
2、访问资源。这一特性主要体现在对ResourceLoader和Resource的支持,因为ApplicationContext都是继承了DefaultResourceLoader的子类。注意:DefaultResourceLoader是AbstractApplicationContext的基类。
3、支持应用事件。继承了接口ApplicationEventPublisher,从而在上下文中引入了时间机制。
4、在ApplicationContext中提供的附加服务。
ApplicationContext容器的设计原理
下面以ApplicationContext系列的FileSystemXmlApplicationContext为例进行论述。可以看到,ApplicationContext应用上下文的主要功能都在基类AbstractXmlApplicationContext中实现了。FileSystemXmlApplicationContext作为具体的应用上下文,只需要实现与其自身设计相关的功能即可。
第一,如果应用直接使用FileSystemXmlApplicationContext,那么对于实例化这个应用上下文的支持,同时启动IoC容器的refresh()过程。代码如下:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 这个 refresh() 过程会涉及 IoC 容器启动的一系列复杂操作
// 对于不同的容器实现,这个操作都是类似的,因此在基类将它们封装好
// 所以在 FileSystemXmlApplicationContext 中只是简单的一个调用
refresh();
}
}
第二,是与FileSystemXmlApplicationContext设计具体相关的功能,这部分涉及怎样从文件系统中加载XML的Bean定义资源。代码如下:
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
IoC容器的初始化过程
简而言之,IoC容器的初始化过程是由
refresh()方法来启动的,这个方法标志着IoC容器的正式启动。
1、第一个过程是Resource定位过程。由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。
2、第二个过程是BeanDefiniton的载入。即,把用户定义好的Bean表示成IoC容器内部的数据结构—— BeanDefinition。
3、第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。通过分析可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。
4、特别的,这里谈的是IoC容器的初始化过程,在这个过程中,一般不包含Bean依赖注入的实现。
BeanDefinition的Resource定位
以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,意味着