一、IOC 概念
IOC 就是一个容器,装水的容器叫做杯子(请不要抬杠),装饭的容器叫饭桶(请不要抬杠),而装 “B ean”的容器叫做 IOC。
二、DI 概念
在图中,Bean4 中的方法调用 Bean2 的 do() 方法,而 Bean2 已经在 IOC 容器中了,这时候直接将容器中的Bean2 实例注入到 Bean4中,这个过程叫做 DI。
在讲解完之后我们再来看看,官方的定义就会直观很多。
IOC(Inversion of Control)控制反转
把原先需要我们手动创建的实例,交由 spring 来创建管理。也就是将对象的控制权限交给Spring,这就叫做控制反转。
DI(Dependency Injection)依赖注入
就是说如果实例 Bean4 需要调用实例 Bean2 中的do()方法,而 Bean2 的对象控制也交由spring来管理,然后再由spring注入到 Bean4 中,这就叫依赖注入。
三、IOC 和 DI 项目中应用
虽说 Spring 功能很是强大,但是spring 只是一个执行命令的工具,并不是万能的神。所以为了能够完成 spring 的工作,spring 针对其使用指定了一系列的规则,而这些规则一般都提供了xml 方式和注解方式。
比方说,现在我们一个项目的代码,我们这些代码需要交由 spring 来管理,那首先我们需要告诉 spring 这些包的路径的地址。
而这一个功能:我们可以通过在 xml 中配置扫描包路径,或者通过注解的方式指定加载包的路径。如下二种方式:
之后我们需要告诉 spring 我们需要将那些类交由 Spring 管理,以及告诉 spring 对象之间的引用关系。如下两种方式:
通过这样我们就能简单的把指定路径下的指定类,以及类之间的对应关系交由 spring 管理。
四、ICO 的规范定义
讲完怎么使用之后,我们来深入了解一下 IOC/DI。
首先我们先看看 spring 中针对 IOC 容器制定的基础容器。如下图:
我们来分别说说这些基础容器定义都有一些什么功能规范:
4.1、最上层规范定义 BeanFactory
/** 位于 spring-beans 项目中 **/
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
..........
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
..........
}
BeanFactory 是 IOC 容器的最上层的接口规范定义,主要定义了 如何从容器中获取对象信息,判断对象属性等操作行为。
再来看看 BeanFacotry 的3个子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。
4.1.1、ListableBeanFactory
/** 位于 spring-beans 项目中 **/
public interface ListableBeanFactory extends BeanFactory {
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
..........
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
..........
}
ListableBeanFactory 是 Beanfacotry 的子类,其提供的功能就是能够定义了能够直接拿到所有 Bean 实例的方法,比 BeanFacotry 直接一个一个获取更给力
4.1.2、HierarchicalBeanFactory
/** 位于 spring-beans 项目中 **/
public interface HierarchicalBeanFactory extends BeanFactory {
@Nullable
// 返回本Bean工厂的父工厂
BeanFactory getParentBeanFactory();
// 本地工厂(容器)是否包含这个Bean
boolean containsLocalBean(String name);
}
HierarchicalBeanFactory 是 Beanfacotry 的子类,HierarchicalBeanFactory 提供的是父容器的访问能力。
4.1.3、AutowireCapableBeanFactory
/** 位于 spring-beans 项目中 **/
public interface AutowireCapableBeanFactory extends BeanFactory {
int AUTOWIRE_NO = 0;
int AUTOWIRE_BY_NAME = 1;
int AUTOWIRE_BY_TYPE = 2;
int AUTOWIRE_CONSTRUCTOR = 3;
..........
<T> T createBean(Class<T> beanClass) throws BeansException;
void autowireBean(Object existingBean) throws BeansException;
Object configureBean(Object existingBean, String beanName) throws BeansException;
..........
}
AutowireCapableBeanFactory 是 Beanfacotry 的子类,AutowireCapableBeanFactory 定义了 Bean 装配的规则。
4.2、看看默认实现 DefaultListableBeanFactory
DefaultListableBeanFactory
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
..........
}
DefaultListableBeanFactory 是容器最终的默认实现类,他继承了 实现了 Spring 针对 IOC 的基础规范定义。
现在有了 IOC/DI 的规范定义之后,那么现在需要定义的是,外界(XML配置文件或者注解,或者其他路径中的文件)读入到内存中需要被管理的对象文件相关信息信息的描述需要进行统一标准,便于管理调用。Spring 给出的定义就是 BeanDefinition 。
BeanDefinition 定义了需要被 spring 管理的 对象描述,以及对象之间的依赖关系。
五、深入了解 IOC 工作原理
IOC 初始化的流程可以分为三步:
- Resource定位。其中 Resource 可以是 xml 配置,注解,Resource 可以存在于本地也可以存在于网络中。
- 载入。 就是解析加载到的配置信息,解析成 BeanDefinition 。
- 注册。处理 Bean 与 Bean 之间的对应关系,之后将这些Bean 放到一个容器中(ConcurrentHashMap)
以 IOC 容器规范的实现类 FileSystemXmlApplicationContext 为例,来看IOC 容器初始化操作
FileSystemXmlApplicationContext 部分继承结构
FileSystemXmlApplicationContext 源码细看 IOC 容器初始化流程
通过查看 FileSystemXmlApplicationContext 源码我们可以发现 其构造函数中无论是有参构造,还是无参构造都需要显示指定 配置文件路径,并且最后都会走到其中的一个构造函数,代码如下:
public FileSystemXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// ①
super(parent);
// ②
setConfigLocations(configLocations);
if (refresh) {
// ③
refresh();
}
}
操作①:super(parent):获取 Spring Resource
主要2个功能:
1、如果有父容器设置父容器,
2、获取 Spring Source 资源加载器。
① super(parent) 最终调用父类 AbstractApplicationContext 中的 setParent(@Nullable ApplicationContext parent) 方法,相关代码如下:
public abstract class AbstractApplicationContext{
..........
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
// AbstractApplicationContext 实例化
this();
// 设置父容器
setParent(parent);
}
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
..........
}
上述代码主要两个操作:
1、AbstractApplicationContext 实例化。
相关代码:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext{
.........
// 上面 this() 方法调用该构造方法
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
// Spring Source 的加载器用于读入 Spring Bean 定义资源文件
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
.........
}
实例化操作,主要是为了获取 Spring Resource 加载器。该加载器是用于读取 Spring Bean 定义资源文件的。
2、在容器拥有父容器的时候进行父容器设置。
操作②:setConfigLocations(configLocations):设置资源文件路径
该操作用来设置资源文件路径
该方法调用的是父类 AbstractRefreshableConfigApplicationContext 中方法,相关代码如下:
public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
implements BeanNameAware, InitializingBean {
..........
public void setConfigLocation(String location) {
// String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
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++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
..........
}
该方法就是用来指定配置文件路径的,通过源码我们也可以知道,其支持多个配置文件同时载入,只需要遵循其定义的 “,; \t\n” 分隔符,进行文件分隔。
操作③:refresh(); 完成IOC 载入
在操作①:super(parent)
和 操作②:setConfigLocations(configLocations)
我们拿到了 Spring Resource Loader
资源加载器,知道了 配置文件路径,也就是说完成了 资源定位
, 操作③:refresh();
来完成 载入
。
refresh() 调用的父类 AbstractApplicationContext 中的方法,代码如下:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 调用准备刷新容器的方法
prepareRefresh();
// 告诉子类调用refreshBeanFactory()方法。开始进行 Bean 定义资源文件载入。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 准备事件处理器等容器特性
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.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
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...
resetCommonCaches();
}
}
}
}
refresh()
完成的工作:
首先在创建 IOC 前,需要把已经存在的容器进行销毁关闭,保证 refresh () 执行之后IOC 容器的唯一性。从 refresh() 字面也可以理解为 重启 IOC 容器的意思,以为会先关闭已有容器,然后重新启动。
而在 refresh() 中开始真正进行 Bean 资源文件载入的是 obtainFreshBeanFactory(); 方法
而在 Bean 资源载入的过程中,需要进行的便是配置文件解析,通过解析 xml 然后更具相应规则,生成 BeanDefition 文件数据集合,然后将数据集合放入到ConcurrentHashMap<>(256) 中。
通过源码追踪 refresh() 执行的关键流程,入口 obtainFreshBeanFactory()
③ AbstractRefreshableApplicationContext#refreshBeanFactory代码如下:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
..........
protected final void refreshBeanFactory() throws BeansException {
// 是如果存在 容器,进行销毁关闭
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建 IOC 容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 进行资源载入
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
..........
}
}
..........
}
这时候可以发现,其创建了一个新的容器 DefaultListableBeanFactory ,之后进行了资源的载入 loadBeanDefinitions(beanFactory);
通过 loadBeanDefinitions(beanFactory) 进行资源载入,包括 xml 解析,然后格式化为 Beandefinition 。
这时候完成了资源的载入。
想具体知道资源文件载入可以通过
AbstractRefreshableApplicationContext#loadBeanDefinitions(beanFactory)
这个入口深入了解。
Bean 注册到 IOC 容器中
Bean 被加载之后,开始被注册。以 DefaultListableBeanFactory 为例。
DefaultListableBeanFactory 会处理各个 Bean 之间的关系 然后将 BeanDefinition 存入 一个ConcurrentHashMap 中。DefaultListableBeanFactory 部分代码如下:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
..........
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
..........
//---------------------------------------------------------------------
// Implementation of BeanDefinitionRegistry interface
//---------------------------------------------------------------------
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
..........
}
}
小结
通过 FileSystemXmlApplicationContext 的三个方法:
1、super(parent);
2、setConfigLocations();
3、refresh();
为入口了解 IOC 容器的初始化过程:设置配置文件地址,通过资源加载器加载资源,最后将其解析成为同一格式的 BeanDefinition 存入 ConcurrentHashMap()中。
六、DI 工作原理
完成 IOC 容器初始化后,IOC 容器中已经管理类定义的相关数据,不过这时候依赖注入还没有进行,依赖注入开始执行是在以下两种情况:
- 第一次通过如getBean() 方法从 IOC 容器中获取 Bean 时,触发依赖注入。
- 配置 lazy-init属性。该属性让容器在解析注册 Bean时就进行预实例化,触发依赖注入。
6.1、第一种、通过 getBean()触发依赖注入
下面我们来看看源码,如何通过 getBean() 触发 DI 。如下 AbstractBeanFactory 中 getBean() 代码
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
..........
//---------------------------------------------------------------------
// Implementation of BeanFactory interface
//---------------------------------------------------------------------
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
// 向容器获取 Bean 实例,同时触发依赖注入
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
..........
// step1、先从单例缓存中获取 Bean
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
..........
}
// step2、缓存中没有则进行创建操作
else {
..........
// step3、创建Bean 是否有依赖Bean,如果存在进行创建
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 递归调用
getBean(dep);
}
}
..........
// 创建 Bean 如果单例
if (mbd.isSingleton()) {
..........
// 返回创建实例
return createBean();
}
// 创建 Bean 如果是原型
else if (mbd.isPrototype()) {
..........
// 返回创建实例
return createBean();
}
// 两种都不是,就是自定义
else{
..........
// 返回创建实例
return createBean();
}
..........
}
..........
}
..........
}
上面的代码是部分代码,忽略了各种操作,例如循环依赖报错问题,有兴趣可以深入看看。
从上面代码可以看出,Bean 在获取的时候,先去缓存中取,没有在创建,创建的时候还包括单例的创建,原型的创建,以及自定义生命周期的Bean 的创建,还有就是 Bean 依赖关系 Bean 的创建。
我们还是以最上图 Bean2 和 Bean4 为例:当我们获取 Bean4 时,发现存在依赖关系 Bean2 就会先创建 Bean2 。如果 Bean2 也存在依赖关系就会递归创建。等Bean2处理完成之后,就是 Bean4 的创建。这样子,Bean4 以及其依赖 Bean2 一起创建完成,这时候 spring 就持有的Bean4 和 Bean2 的实例。依赖注入的发生,就是在 Bean4 创建完成的同时进行。而创建的方法就是上面代码中的 createBean();
我们继续深入 createBean() 中一探究竟,从该入口进入,会发现该方法由其子类 AbstractAutowireCapableBeanFactory 实现,而真正实现的是 doCreateBean()方法:代码 如下:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
..........
//执行依赖注入
populateBean(beanName, mbd, instanceWrapper);
..........
}
该方法是执行依赖注入的入口,针真正执行逻辑的是 applyPropertyValues() 方法,在里面可以看到 JDK 反射进行赋值操作。有兴趣的小伙伴可以从该入口中点击进入查看。
6.2、第二种、配置 lazy-init属性 的 Bean 在容器初始化时进行实例化
在上面我们讲到了 FileSystemXmlApplicationContext 真正执行的容器初始化操作实在 refresh() 方法中。在我们再来看看部分代码:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
..........
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
..........
// 告诉子类调用refreshBeanFactory()方法。开始进行 Bean 定义资源文件载入
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
..........
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
..........
}
}
..........
}
其中 obtainFreshBeanFactory() 完成了加载配置文件,然后将其解析成 BeanDefinition 存入 ConcurrentHashMap 中,完成 IOC 容器的基础数据的准备。之后的 finishBeanFactoryInitialization(beanFactory)方法会把需要预实例化的Bean 进行实例。
以上就是 FileSystemXmlApplicationContext IOC 容器初始化和DI 操作。FileSystemXmlApplicationContext 是基于 XML 的解析操作,而 注解的操作本质都是一样,有兴趣的小伙伴可以通过 AnnotationConfigApplicationContext 进行源码分析学习。
七、总结
针对以上的源码分析,我们对 IOC 和 DI 有了深刻的了解。
IOC 就是一个存放 Bean 实例的容器。
Spring 根据我们配置的对象以及对象关系的配置文件(资源定位),根据特定的方式进行解析,解析成统一的格式BeanDefinition(载入),之后将这些 BeanDefinition 存放进入一个 ConcurrentHashMap 容器中(注册)。
DI 就是一个操作。
根据 BeanDefinition 中定义的对象依赖关系,通过反射,进行赋值。
而DI 的触发的方式有两种,主要根据 BeanDefinition 的配置 lazy-init 有关,如果需要懒加载 也就是 lazy-init=true ,则在调用 getBean() 方法是触发Bean 实例化以及依赖注入,而如果lazy-init=false 则在容器初始化过程中进行依赖注入。