一、Spring IOC容器概述
维基百科定义: 控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
需要注意的是,这里的反转指的是责任的反转。 例如A-> B(即A依赖于B),传统方式下是 A直接newB对象,例如A{ new B() }; 假如是IOC ,则是通过@Autowired(举例),不再是由A直接创建(管理)B对象,B对象的生命周期(创建、初始化、属性赋值、销毁...)是交给了容器管理; 这既是责任的反转,B对象的管理由A交给了IOC容器。
技术描述: Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式地用 new 创建 B 的对象。 采用依赖注入技术之后,A的代码只需要定义一个 private 的B对象,不需要直接 new 来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中,而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。(其实就是把bean的创建和依赖关系的管理交给了容器)
实现方法: 实现控制反转主要有两种方式:依赖注入和依赖查找。
ℹ︎依赖注入: 依赖注入(dependency injection,缩写为 DI)是一种软件设计模式,也是实现控制反转的其中一种技术。这种模式能让一个对象接收它所依赖的其他对象。“依赖”是指接收方所需的对象。“注入”是指将“依赖”传递给接收方的过程。在“注入”之后,接收方才会调用该“依赖”。此模式确保了任何想要使用给定服务的对象不需要知道如何创建这些服务。取而代之的是,连接收方对象(像是 client)也不知道它存在的外部代码(注入器)提供接收方所需的服务。
1、构造器注入
public class A{
private B b;
public A(B b){
this.b = b;
}
}
2、setter注入
public class A{
private B b;
public void setb(B b) {
this.b = b;
}
}
3、属性注入
public class A{
@Autowired
private B b;
}
ℹ︎依赖查找: 1、依赖拖曳(Dependency Pull) 依赖拖曳获取容器的方法是在代码中写死的。其逻辑是从配置文件(.properties,xml文件等)中生成容器,然后再从容器中查找需要的依赖对象。也就是说,依赖对象必须在指定的配置文件中配置好。
public static void main(String[] args) {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("classpath:bean.xml");
Object man = context.getBean("man");
}
2、上下文查找
public CleanerProcessor getProxy() {
return ApplicationContextUtils.getBean(CleanerProcessor.class);
}
PS:我以前一直没搞明白控制反转和依赖注入的区别,以为是一样的,其实不是的,依赖注入只是实现控制反转思想的方法之一
二、Spring IOC容器的设计与实现
上图为IOC容器中的主要接口设计,里面有两个核心容器类,BeanFactory和ApplicationContext。可以理解为BeanFactory是基础的容器,定义了一些容器最基本的功能,而ApplicationContext为高级容器,具有比较多的高级功能。
对于上图的理解,只要看两条路线即可:
- BeanFactory设计路线:BeanFactory->HierarchicalBeanFactory->ConfigurableBeanFactory
- ApplicationContext设计路线:BeanFactory -> ListableBeanFactory -> ApplicationContext -> WebApplicationContext / ConfigurableApplicationContext
BeanFactory的接口具备了BeanFactory IoC容器的基本功能另一方面通过继承MessageSource 、 ResourceLoadr 、ApplicationEventPublisher这些接口BeanFactory为ApplicationContext赋予了更高级的IoC容器特性。对于ApplicationContext而言为了在Web环境中使用它还设计了WebApplicationContext接口而这个接口通过继承ThemeSource接口来扩充功能。
三、IOC容器的初始化过程
以FileSystemXmlApplicationContext
为例对源码进行解析
main:
public static void main(String[] args) {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("classpath:bean.xml");
Object man = context.getBean("man");
}
bean.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean class="com.ppz.eureka_client_1.iocTest.model.Hello" id="man">
<!--普通类型的set注入-->
<property name="name" value="pzx"/>
<property name="sex" value="man"/>
</bean>
</beans>
3.1 BeanDefinition的Resource定位
找到BEAN定义资源,在此文中即找到”bean.xml“文件,最后的实现在DefaultResourceLoader.getResource完成对Resource的定位
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)
3.2 BeanDefinition的载入和解析
其实我是定位到 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyValue,再去看调用栈。
bean定义载入: 将xml的文档载入为InputStream,然后通过DefaultDocumentLoader将stream转换为Document,就完成了bean的载入 org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
bean定义解析: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)
3.3 BeanDefinition在IOC容器的注册
可以看到IOC的基础数据结构就是个Map,this.beanDefinitionMap 核心方法: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
调用图: