IoC容器和依赖注入
IoC容器和依赖注入是Spring框架最为核心的功能,各种实现其他功能的框架都是在此基础上发展而来的。
核心概念
什么是IoC(Inverse of Control)容器和依赖注入呢?这两个概念,实际上是面向对象编程发展到比较高级的阶段后提出的,其本质就是将对象的组装和实例化通过声明的方式,将实例化的工作交给Spring来管理。
我们可以通过一个例子来理解:假设我们有一个MyTool组件,其中实现了一些功能,然而,为了保证代码可复用性,我们的MyTool必须依赖一个ToolConfig配置对象来启动,其中包含了MyTool启动的必要参数。
ToolConfig.java
package com.gacfox.demo;
public class ToolConfig {
private String mode;
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
}
MyTool.java
package com.gacfox.demo;
public class MyTool {
private ToolConfig toolConfig;
public ToolConfig getToolConfig() {
return toolConfig;
}
public void setToolConfig(ToolConfig toolConfig) {
this.toolConfig = toolConfig;
}
public void handleWork() {
System.out.println("I'm working on " + toolConfig.getMode() + "mode...");
}
}
如果不使用Spring框架,我们可能会编写类似如下代码装配使用MyTool组件:
// 创建配置组件
ToolConfig toolConfig = new ToolConfig();
toolConfig.setMode("promiscuous");
// 创建组件并载入配置
MyTool myTool = new MyTool();
myTool.setToolConfig(toolConfig);
// 具体使用组件的功能
myTool.handleWork();
乍一看上去好像也没什么不妥,但一旦我们的项目复杂起来,情况就不一样了。手动管理这些组件对象,需要我们手动装配对象间的依赖关系,还要手动进行单例、多例模式的编写(上面编写的是多例模式,维护单例模式会更加复杂),随着项目的规模越来越庞大,经手的人越来越多,整个架构就会变得越来越难以维护。
这时,Ioc和依赖注入的概念自然就出现了。我们将对象统一交给IoC容器来管理,只需编写声明式配置告诉IoC容器,如何将这个对象和其依赖对象实例化出来,我们需要的时候,直接从容器中取出的就是装配好的对象,这就方便太多了!
下面是通过Spring的声明方式装配MyTool组件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="toolConfig" class="com.gacfox.demo.ToolConfig">
<property name="mode" value="promiscuous" />
</bean>
<bean id="myTool" class="com.gacfox.demo.MyTool">
<property name="toolConfig" ref="toolConfig" />
</bean>
</beans>
取用MyTool组件:
// 启动Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过Bean名获取对象
MyTool myTool = context.getBean("myTool", MyTool.class);
// 具体使用组件的功能
myTool.handleWork();
其实我们可以发现,我们代码分为了两个部分:xml中编写的配置指定了如何装配SpringBean,而具体的装配操作则是交给Spring框架来执行了;我们实际Java代码中,直接从Spring的容器中取出对象,就可以直接使用了。
BeanFactory容器接口
BeanFactory即SpringBean工厂,我们通过声明式配置定义如何装配对象,那么实际上具体操作就是由BeanFactory完成的,从Spring容器中获取Bean,也是经由BeanFactory来实现。

BeanFactory对功能的接口划分十分详细直观,具体通过上图参考相关文档即可。
实际上,SpringBean定义在框架中抽象为了BeanDefinition对象,我们可以手动创建BeanDefinition并注册到BeanFactory中,这种用法在很多框架底层代码中十分常见。下面是例子代码:
package com.gacfox.demo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* @author gacfox
*/
public class MyToolManager implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
// 创建ToolConfig的BeanDefinition
BeanDefinition toolConfigDef = BeanDefinitionBuilder
.rootBeanDefinition(ToolConfig.class)
.addPropertyValue("mode", "promiscuous")
.getBeanDefinition();
// 注册toolConfig
beanFactory.registerBeanDefinition("toolConfig", toolConfigDef);
// 创建myTool的BeanDefinition
BeanDefinition myToolDef = BeanDefinitionBuilder
.rootBeanDefinition(MyTool.class)
.addPropertyReference("toolConfig", "toolConfig")
.getBeanDefinition();
// 注册myTool
beanFactory.registerBeanDefinition("myTool", myToolDef);
}
}
上面代码中,我们手动创建了BeanDefinition对象,并注册到了BeanFactory中。这和之前xml配置的效果相同。
注意这里获取BeanFactory的方式,MyToolManager实际也是一个SpringBean,由xml配置进行加载,该类实现了BeanFactoryPostProcessor接口,这个接口实际上是一个钩子函数,用于在BeanFactory初始化完成后进行一些处理。在底层框架功能开发中,我们会大量用到此类钩子函数。
这种写法在学习Spring的初级阶段大概不会遇到,然而,手动创建BeanDefinition实际上对底层功能开发非常有用,比如我们开发的一个框架需要通过注解方式加载Bean,那么我们就需要通过反射找到标注了注解的类,并手动创建BeanDefinition。
父子容器
BeanFactory容器可以具有父子关系,实际上我们日常开发中,SpringMVC的Bean就被创建在子容器中。父子容器具有如下关系:
- 子容器可以访问父容器的Bean,反之则不行
- 同一容器内不能有
id相同的Bean,但子容器中可以有和父容器id相同的Bean
我们在进行一些底层功能开发时,可以创建一个单独的BeanFactory子容器,使得我们的Bean具有更加良好的封装性。
ApplicationContext应用上下文
ApplicationContext接口代表以BeanFactory容器为核心的当前整个Spring应用上下文,我们自己翻阅源码其实会发现ApplicationContext继承了包括BeanFactory在内的许多接口,它是一个更高级的抽象。

ApplicationContext是Spring框架的启动入口,我们之前使用过的ClassPathXmlApplicationContext就是用于从xml文件中启动Spring应用上下文。