IoC容器和依赖注入
IoC容器和依赖注入是Spring框架中最为核心的功能,几乎所有基于Spring的框架都是在此基础上发展而来的,我们这里详细了解一下相关的概念和用法。
核心概念
什么是IoC(Inverse of Control)容器和依赖注入呢?这两个概念实际上是面向对象编程发展到比较高级的阶段后提出的,其本质就是将对象的组装和实例化通过声明的方式,将实例化的工作交给Spring来管理,我们可以通过一个例子来理解。
假设我们有一个MyTool组件,其中实现了一些功能,然而为了保证代码可复用性,我们的MyTool必须依赖一个ToolConfig配置对象来启动,其中包含了MyTool启动的必要参数,相关类定义如下。
ToolConfig.java
package com.gacfox.demo;
import lombok.Data;
@Data
public class ToolConfig {
private String mode;
}
MyTool.java
package com.gacfox.demo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
public class MyTool {
private ToolConfig toolConfig;
public void handleWork() {
log.info("I'm working on {} mode...", toolConfig.getMode());
}
}
如果不使用Spring框架,我们可能会编写类似如下代码装配使用MyTool组件。
package com.gacfox.demo;
public class Main {
public static void main(String[] args) {
// 创建配置组件
ToolConfig toolConfig = new ToolConfig();
toolConfig.setMode("promiscuous");
// 创建组件并载入配置
MyTool myTool = new MyTool();
myTool.setToolConfig(toolConfig);
// 具体使用组件的功能
myTool.handleWork();
}
}
乍一看上去好像也没什么不妥,但一旦我们的项目复杂起来,情况就不一样了。手动管理这些组件对象,需要我们手动装配对象间的依赖关系,还要手动进行单例、多例模式的编写(上面编写的是多例模式,维护单例模式会更加复杂),随着项目的规模越来越庞大,经手的人越来越多,整个架构就会变得越来越难以维护。
这时,IoC和依赖注入的概念自然就出现了。我们将对象统一交给IoC容器来管理,只需编写声明式配置告诉IoC容器,如何将这个对象和其依赖对象实例化出来,我们需要的时候,直接从容器中取出的就是装配好的对象,这就方便太多了!
下面是通过Spring的XML声明方式装配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"
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">
<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组件的代码就变成了下面的写法。
package com.gacfox.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
// 启动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来实现。
classDiagram
BeanFactory <|-- ListableBeanFactory
BeanFactory <|-- HierarchicalBeanFactory
HierarchicalBeanFactory <|-- ConfigurableBeanFactory
ListableBeanFactory <|-- ConfigurableListableBeanFactory
ConfigurableBeanFactory <|-- ConfigurableListableBeanFactory
AutowireCapableBeanFactory <.. AbstractAutowireCapableBeanFactory
SingletonBeanRegistry <|-- DefaultSingletonBeanRegistry
DefaultSingletonBeanRegistry <|-- AbstractBeanFactory
AbstractBeanFactory <|-- AbstractAutowireCapableBeanFactory
AbstractAutowireCapableBeanFactory <|-- DefaultListableBeanFactory
DefaultListableBeanFactory <|-- XmlBeanFactory
BeanDefinitionRegistry <|.. DefaultListableBeanFactory
ConfigurableListableBeanFactory <|.. DefaultListableBeanFactory
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;
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);
}
}
上面代码中,我们手动创建了ToolConfig和MyTool的BeanDefinition对象并注册到了BeanFactory中,在XML配置中,我们需要注册这个BeanFactory。
<?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"
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">
<bean id="myToolManager" class="com.gacfox.demo.MyToolManager"/>
</beans>
可以看到,这里的BeanFactory实际也是一个SpringBean,它最终还是由XML配置进行加载,该类实现了BeanFactoryPostProcessor接口,这个接口实际上是一个钩子函数,用于在BeanFactory初始化完成后进行一些处理。在底层框架功能开发中,我们会大量用到此类钩子函数。
这种写法在学习Spring的初级阶段大概不会遇到,然而,手动创建BeanDefinition实际上对底层功能开发非常有用,比如我们开发的一个框架需要通过注解方式加载Bean,那么我们就需要通过反射找到标注了注解的类,并手动创建BeanDefinition然后注册对应SpringBean。
父子容器
Spring的IoC容器可以具有父子关系,如果你曾使用过传统方式(非SpringBoot)手动搭建的Spring + SpringMVC项目,其中SpringMVC的Bean默认就会被创建在子容器中。父子容器具有如下关系。
- 子容器可以访问父容器的Bean,反之则不行
- 同一容器内不能有
id相同的Bean,但子容器中可以有和父容器id相同的Bean
父子容器机制的意义在于一些底层功能开发时,可以允许我们创建单独的子容器,使得我们的Bean具有更加良好的封装性。
ApplicationContext应用上下文
ApplicationContext接口代表以IoC容器为核心的当前整个Spring应用上下文,我们自己翻阅源码其实会发现ApplicationContext继承了包括BeanFactory在内的许多接口,它是一个更高级的抽象。
classDiagram
class ApplicationEventPublisher
class MessageSource
class ResourceLoader
class ResourcePatternResolver
class BeanFactory
class HierarchicalBeanFactory
class ListableBeanFactory
class ApplicationContext
class LifeCycle
class ConfigurableApplicationContext
class AbstractApplicationContext
class GenericApplicationContext
class AbstractRefreshableApplicationContext
class AnnotationConfigApplicationContext
class AbstractRefreshableConfigApplicationContext
class GenericGroovyApplicationContext
class AbstractXmlApplicationContext
class ClassPathXmlApplicationContext
class FileSystemXmlApplicationContext
ApplicationEventPublisher <|-- ApplicationContext
MessageSource <|-- ApplicationContext
ResourceLoader <|-- ApplicationContext
ResourcePatternResolver <|-- ApplicationContext
BeanFactory <|-- HierarchicalBeanFactory
HierarchicalBeanFactory <|-- ApplicationContext
ListableBeanFactory <|-- ApplicationContext
LifeCycle <|-- ConfigurableApplicationContext
ApplicationContext <|-- ConfigurableApplicationContext
ConfigurableApplicationContext <|-- AbstractApplicationContext
AbstractApplicationContext <|-- GenericApplicationContext
AbstractApplicationContext <|-- AbstractRefreshableApplicationContext
GenericApplicationContext <|-- AnnotationConfigApplicationContext
GenericApplicationContext <|-- GenericGroovyApplicationContext
AbstractRefreshableApplicationContext <|-- AbstractRefreshableConfigApplicationContext
AbstractRefreshableConfigApplicationContext <|-- AbstractXmlApplicationContext
AbstractXmlApplicationContext <|-- ClassPathXmlApplicationContext
AbstractXmlApplicationContext <|-- FileSystemXmlApplicationContext
ApplicationContext是Spring框架的启动入口,我们之前使用过的ClassPathXmlApplicationContext其实就是基于XML配置文件启动Spring应用上下文。