Spring源码学习【一】初识IOC容器
目录
一、IOC和DI
IOC(Inversion Of Control):控制反转,是Spring的核心。所谓控制反转,就是将对象的创建和管理交由Spring容器控制,这是一种重要的面向对象设计思想,能够帮助我们设计出低耦合的程序。
DI(Dependency Injection):依赖注入。在运行期间,动态地为对象注入其所依赖的对象,提高组件的复用,提高程序的灵活性和可扩展性。
控制反转和依赖注入是对同一概念的不同描述,控制反转以容器的角度描述,而依赖注入则是以应用程序的角度描述,这两种说法殊途同归,分离了对象和其所依赖的资源,使得程序更加灵活,为我们提供了重要的面向对象设计思想。
二、Spring的IOC容器设计
Spring中有两个系列的容器:
1. 实现BeanFactory接口的简单IOC容器,这些容器提供了IOC容器的基本功能。
2. 实现ApplicationContext接口的应用上下文,这些应用上下文不仅提供了IOC容器的基本功能,而且提供了一些高级功能,是IOC容器的高级形式。
IOC容器接口设计图如图1所示,Spring设计了如此之多的IOC容器接口,一方面可以通过接口的叠加来扩展IOC容器的功能,另一方面可以方便特定容器的定制实现。
(一)BeanFactory系列容器接口
从图1可以看到,BeanFactory是IOC容器接口中最顶层的接口,定义了IOC容器的基本规范。BeanFactory共有三个直接的子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。
ListableBeanFactory接口表示Bean是可列表化的。
HierarchicalBeanFactory接口表示Bean是有继承关系的,可以管理父IOC容器。
AutowireCapableBeanFactory接口表示Bean是自动装配的。
从图1中可以看到,BeanFactory系列接口中一个主要的继承路径就是从BeanFactory到ConfigurableListableBeanFactory。在HierarchicalBeanFactory接口的基础上,扩展了对BeanFactory的配置功能,如设置父IOC容器、配置Bean后置处理器等。
BeanFactory接口定义了IOC容器最基本的形式,这些功能的定义可以在BeanFactory接口中看到,通过这一系列的接口方法,可以实现不同的IOC容器,接口代码如下所示:
public interface BeanFactory {
/**
* 转义符号,用于获取FactoryBean本身(即用于生产和管理Bean的Bean)
* 在Spring中,所有的Bean都是有BeanFactory进行管理的,而BeanFactory是一个特殊的FactoryBean
* 通过getBean(&factoryBeanName)会返回名为factoryBeanName的FactoryBean
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* 根据名字获取指定的Bean实例,此方法使Spring BeanFactory可以当做单例或原型模式来使用
* Bean的别名会被翻译为规范的Bean名称,如果在工厂实例中找不到对应的Bean,则会询问父工厂
*/
Object getBean(String name) throws BeansException;
/**
* 根据指定的名字获取Bean实例,并根据Class类型对Bean进行类型检查
* 当Bean的类型与requiredType不一致时会抛出BeanNotOfRequiredTypeException
*/
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
/**
* 根据指定的名字获取Bean实例,可以指定创建Bean时需要的构造参数,且会覆盖BeanDefinition
* 中的默认参数,参数仅用于创建新实例
* 当给定了构造参数但Bean不是原型作用域时,会抛出BeanDefinitionStoreException
* 原因是Spring中Bean默认的作用域是容器内的单例,要想在同一个容器中使用不同的Bean实例,则需要为
* Bean指定原型作用域(scope="prototype")
*/
Object getBean(String name, Object... args) throws BeansException;
/**
* 根据指定的名字获取Bean实例,可以指定创建Bean时需要的构造参数,且会覆盖BeanDefinition
* 中的默认参数,参数仅用于创建新实例
*/
<T> T getBean(Class<T> requiredType) throws BeansException;
/**
* 返回与给定对象类型唯一匹配的bean实例,可指定Bean的构造参数
* 当匹配到多个Bean时会抛出NoUniqueBeanDefinitionException
* 当给定了构造参数但Bean不是原型作用域时,会抛出BeanDefinitionStoreException
*/
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* 是否存在给定名称的Bean
* 注意:由于BeanDefinition的具体或抽象、懒加载或饿加载、作用域的不同,该方法返回true不代表
* getBean(String name)会获得一个Bean实例
*/
boolean containsBean(String name);
/**
* 这个Bean是否对应于一个单例
* 注意:由于Bean的作用域有单例(singleton)、原型(prototype)、会话(session)、请求(request)
* 等,该方法返回false只代表Bean是非单例的
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* 这个bean是否总是提供独立的实例
* 注意:由于Bean的作用域有单例(singleton)、原型(prototype)、会话(session)、请求(request)
* 等,该方法返回false只代表Bean是非原型的
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* 检查给定名称的Bean是否与指定类型匹配
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionEx-ception;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinition-Exception;
/**
* 获得给定名称的Bean的Class类型
*/
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
/**
* 获得给定名称的Bean的别名
*/
String[] getAliases(String name);
}
BeanFactory接口只定义了IOC容器的基本行为,忽略了IOC容器的具体实现,要知道工厂是如何生产和管理对象的,就需要深入到具体IOC容器的实现,DefaultListableBeanFactory、XmlBeanFactory等就是基本IOC容器的具体实现,下面以XmlBeanFactory为例看看IOC容器的简单实现。
XmlBeanFactory是一个简单的IOC容器的实现,它继承自DefaultListableBeanFactory。Spring中将DefaultListableBeanFactory作为一个默认的功能完整的IOC容器使用,XmlBeanFactory在DefaultListableBeanFactory的基础上扩展了读取以XML格式定义的BeanDefinition(Spring中描述Bean的类)的功能,XmlBeanFactory源码如下所示:
public class XmlBeanFactory extends DefaultListableBeanFactory {
/**
* XmlBeanDefinitionReader用于处理以XML文件格式定义的BeanDefinition
*/
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* 根据给定的Resource创建一个XmlBeanFactory
* Resource是Spring用来封装I/O操作的类
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* 根据给定的Resource和父工厂创建一个XmlBeanFactory,并使用XmlBeanDefinitionReader对象
* 加载BeanDefinitions
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
下面尝试使用XmlBeanFactory,代码如下:
ClassPathResource resource = new ClassPathResource("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(resource);
可见XmlBeanFactory的使用是非常简单的,但这个类已经在Spring3.1中被标明废弃。XmlBeanFactory继承自DefaultListableBeanFactory,是对后者的功能扩展,我们可以通过编程直接使用后者来达到前者的功能,代码如下:
ClassPathResource resource = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
以上代码使用了DefaultListableBeanFactory这一功能功能完整的IOC容器,主要分为以下四个步骤:
1. 创建IOC容器配置文件的资源,这里使用ClassPathResource,即在ClassPath中寻找资源文件。
2. 创建一个BeanFactory,这里使用了DefaultListableBeanFactory。
3. 创建一个BeanDefinition读取器,这里使用XmlBeanDefinitionReader,用于加载以XML文件格式定义的BeanDefinition,并回调给BeanFactory。
4. 从资源中读取Bean的配置信息,并完成Bean的加载和注册。
(二)ApplicationContext系列容器接口
ApplicationContext作为一系列重要的容器产品,一方面继承了BeanFactory基本的IOC容器功能;另一方面,对BeanFactory进行了功能扩展。
如图2所示,ApplicationContext通过继承ApplicationEventPublisher、ResourcePatternResolver、MessageSource、EnvironmentCapable接口,为基本的IOC容器扩展了高级的容器特性,例如:
ApplicationEventPublisher:支持应用事件
MessageSource:支持国际化信息源
ResourcePatternResolver: 允许以路径模式定位Resource
EnvironmentCapable:应用上下文环境检查
FileSystemXmlApplicationContext和ClassPathXmlApplicationContext是常用的应用上下文,根据名字可以看出二者分别实现了从文件系统和类路径读取以XML格式定义的BeanDefinition的功能,下面以FileSystemXmlApplicationContext为例看一看应用上下文的设计原理。
FileSystemXmlApplicationContext的类继承关系如下图所示。
关于应用上下文的主要功能已经在其父类AbstractXmlApplicationContext中实现了,而FileSystemXmlApplicationContext主要进行了两方面的功能扩展:
1. 构造应用上下文实例,同时启动IOC容器的refresh()过程,代码如下
/**
* 创建一个应用上下文,根据应用环境解析路径,并启动refresh()过程
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
refresh()中具体的处理过程已经定义在AbstractApplicationContext中,在子类应用上下文中只需要显式调用即可。refresh()中进行了刷新准备、创建BeanFactory、注册消息源等一系列操作。
2. 定义从文件路径加载XML资源文件的方法,代码如下:
/**
* 通过文件系统路径解析资源文件
*/
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
FileSystemXmlApplicationContext重写了DefaultResourceLoader中的getResourceByPath(String path)方法,提供了通过文件系统路径定位资源文件的功能。在DefaultResourceLoader中,默认实现是通过ClassPath加载资源文件的,代码如下所示:
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
参考资料:《Spring技术内幕》
上一篇: 初识Spring Boot框架
下一篇: Spring入门及初识IOC
推荐阅读
-
Spring源码剖析2:Spring IOC容器的加载过程
-
Spring源码分析之IoC容器初始化
-
Spring 框架学习第三节:核心理念之一 —— IoC(控制反转)
-
Spring源码学习(一):Spring容器创建和初始化工作准备
-
荐 Spring 学习笔记①:IoC容器、Bean与注入
-
Spring源码分析-IOC容器BeanFactory的应用场景
-
Spring IOC 容器源码分析
-
Spring源码学习【六】AOP原理解析(一)代理对象的生成
-
Spring框架源码分析(IoC):BeanFactory和ApplicationContext容器家族
-
带你从零开始手写 spring ioc 框架,深入学习 spring 源码