计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring IOC控制反转通用原理总结 spring控制反转的原理

btikc 2024-10-15 09:02:40 技术文章 8 ℃ 0 评论

Spring IOC控制反转通用原理总结

一、Spring容器高层视图

Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。

二、IOC实现原理

使用反射机制+XML技术。

理解了这些基本概念后,我们通过一个简单的示意图来简单描述一下整个流程:


从示意图可以看出,当web容器启动的时候,spring的全局bean的管理器会去xml配置文件中扫描的包下面获取到所有的类,并根据你使用的注解,进行对应的封装,封装到全局的bean容器中进行管理,一旦容器初始化完毕,beanID以及bean实例化的类对象信息就全部存在了,现在我们需要在某个service里面调用另一个bean的某个方法的时候,我们只需要依赖注入进来另一个bean的Id即可,调用的时候,spring会去初始化完成的bean容器中获取即可,如果存在就把依赖的bean的类的实例化对象返回给你,你就可以调用依赖的bean的相关方法或属性等;


2.1BeanFactory

BeanFactory体系架构:

BeanDefinitionRegistry: Spring 配置文件中每一个<bean>节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。

BeanFactory 接口位于类结构树的顶端 ,它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展:

ListableBeanFactory:该接口定义了访问容器中 Bean 基本信息的若干方法,如查看Bean 的个数、获取某一类型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;

HierarchicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。

ConfigurableBeanFactory:是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;

AutowireCapableBeanFactory:定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;

SingletonBeanRegistry:定义了允许在运行期间向容器注册单实例 Bean 的方法;


2.2例子

使用 Spring 配置文件为 Car 提供配置信息:beans.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="Index of /schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xsi:schemaLocation="Index of /schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


<bean id="car1" class="com.gongfu.Car"

p:brand="大门A5"

p:color="绿色"

p:maxSpeed="300" />

</beans>

通过 BeanFactory 装载配置文件,启动 Spring IoC 容器:

public class BeanFactoryTest {

public static void main(String[] args) throws Throwable{

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

Resource res = resolver.getResource("classpath:com/baobaotao/beanfactory/beans.xml");

BeanFactory bf = new XmlBeanFactory(res);

System.out.println("init BeanFactory.");

Car car = bf.getBean("car",Car.class);

System.out.println("car bean is ready for use!");

}

XmlBeanFactory 通过 Resource 装载 Spring 配置信息并启动 IoC 容器,然后就可以通过 BeanFactory#getBean(beanName)方法从 IoC 容器中获取 Bean 了。通过 BeanFactory 启动IoC 容器时,并不会初始化配置文件中定义的 Bean,初始化动作发生在第一个调用时。

对于单实例( singleton)的 Bean 来说,BeanFactory会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从 IoC 容器的缓存中获取 Bean 实例。Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用HashMap 实现的缓存器,单实例的 Bean 以 beanName 为键保存在这个HashMap 中。

值得一提的是,在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错。


三、ApplicationContext

ApplicationContext 由 BeanFactory 派生而来,提供了更多面向实际应用的功能。

在BeanFactory 中,很多功能需要以编程的方式实现,而在 ApplicationContext 中则可以通过配置的方式实现。


ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其他的接口扩展了 BeanFactory 的功能:

ClassPathXmlApplicationContext:默认从类路径加载配置文件

FileSystemXmlApplicationContext:默认从文件系统中装载配置文件

ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件 , 并对事件进行响应处理 。 在 ApplicationContext 抽象实现类AbstractApplicationContext 中,我们可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。

MessageSource:为应用提供 i18n 国际化消息访问的功能;

ResourcePatternResolver : 所 有 ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。

LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。

ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要的方法: refresh()和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。


四、BeanDefinition

SpringIOC容器帮我们管理了各种bean对象及其相互依赖的关系,Bean对象在Spring实现中是以BeanDefinition来描述的。*换句话说 依赖关系是使用BeanDefinition来保存的。 *其继承体系如下:


Spring关于Bean的装配,有下面三种方式:

在XML中进行显示配置。

在java中进行显示配置。

隐式的bean发现机制和自动装配。

Bean的解析非常的复杂,功能被分的很细,需要扩展的地方也很多,必须保证有足够的灵活性。


4.1IOC容器的初始化

IOC容器的初始化包括:BeanDefinition的Resouce定位,BeanDefinition的载入和注册三个基本的过程。需要注意的是Spring把上面过程进行了分离,并使用了不同的模块来完成,定位使用了ResourceLoader,解析使用了BeanDefinitionReader等。这样设计的目的可以让用户对这三个过程进行裁剪和扩展,定义出适合自己的IOC容器初始化过程。


下面来看一下两种IOC容器的创建过程,BeanFactory以XmlBeanFactory为例,ApplicationContext 以 FileSystemXmlApplicationContext为例。

XmlBeanFactroy

看一下XmlBeanFactory的源码定义:

public class XmlBeanFactory extends DefaultListableBeanFactory {

//定义一个BeanDefinitionReader阅读器

private final XmlBeanDefinitionReader reader;

//构造方法,需要传入Resource,资源的定位

public XmlBeanFactory(Resource resource) throws BeansException {

this(resource, (BeanFactory)null);

}

//构造方法重载,需要传入Resource,和父容器,此方法完成了读取器的初始化操作

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {

super(parentBeanFactory);

this.reader = new XmlBeanDefinitionReader(this);

this.reader.loadBeanDefinitions(resource);

}

}

通过阅读XmlBeanFactory的源码可以看出。

XmlBeanFactory继承自DefaultListableBeanFactory类。而DefaultListableBeanFactory类包含了基本IOC容器所具有的重要功能。

那BeanDefinition的信息来源自哪,如何定位? 定位之后又如何读取解析呢??

对于信息来源的定位封装成Spring中的Resource类来给出,解析是它的内部定义了一个XmlBeanDefinitionReader对象,有了这个对象,就有了处理xml文件的能力。

参考XmlBeanFactory的实现,我们手动来模拟一下XmlBeanFactory的实现过程:

//首先进行BeanDefinition资源文件的定位,封装为Source的子类

ClassPathResource res = new ClassPathResource("beans.xml");

//创建基本的IOC容器

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

//创建文件读取器,并进行回调设置。

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

//资源加载解析

reader.loadBeanDefinitions(res);

通过上面的代码,总结一下IOC容器的使用步骤:


创建IOC抽象资源,这个抽象资源包含了BeanDefinition的定义信息

创建一个BeanFactory

创建一个BeanDefinition的读取器,通过一个回调配置给BeanFactory

从定义好的抽象资源中读取配置信息。完成载入和注册的定义后,IOC容器就建立了起来了。

FileSystemXmlApplicationContext

看一下这个类的源码实现:

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

//构造方法重载,省略其他的构造方法

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {

super(parent);

this.setConfigLocations(configLocations);

if(refresh) {

this.refresh();

}

}

//通过一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition

protected Resource getResourceByPath(String path) {

if(path != null && path.startsWith("/")) {

path = path.substring(1);

}

return new FileSystemResource(path);

}

}

除了构造方法和getResourceByPath外,并没有其他的方法。也就说它只是实现了和它自身设计相关的两个功能。

看一下该类的调用方式:

ApplicationContext context =new FileSystemXmlApplicationContext(xmlPath);

上面调用了参数是字符串的构造函数,但是,对构造函数的调用最终都会转到下面的方法来执行,refresh方法启动了整个容器的初始化流程:

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {

super(parent);

this.setConfigLocations(configLocations);

if(refresh) {

this.refresh();

}

}

在资源定位的时候,支持下面两种方式:


ClasspathResource res = new ClasspathResource("a.xml,b.xml");

ClasspathResource res = new ClasspathResource("new String(){'a.xml' , 'b.xml'}")

我们来看以上,程序执行到此处后,都做了哪些操作:


在AbstractApplicationContext中初始化了resourcePatternResolver(多资源文件的载入),用于获取Resource,关于何时使用后面再解释

将资源的定义路径保存在了configLocations数组中

到此,IOC容器根据资源定义路径获取Resouce的准备工作便完成了。

IOC容器的初始化过程:

在开始分析初始化过程之前,先从宏观上对初始化的过程做一个简单的介绍,有一个大体框架的初始化模型,方便后面的理解,初始化过程分成了三个过程:

Resource定位

BeanDefinition的载入:

向IOC容器注册这些BeanDefinition信息。这个过程是通过BeanDefinitionRegistry接口的实现来完成的。最终注入到HashMap中去,这个hashmap就是持有beandefinition数据的。


五、IOC容器工作机制

容器启动过程

web环境下Spring容器、SpringMVC容器启动过程:

首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;

其次,在web.xml中会提供有contextLoaderListener(或ContextLoaderServlet)。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring容器以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;

再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例(Spring MVC),这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文容器,用以持有spring mvc相关的bean,这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文(即第2步中初始化的XmlWebApplicationContext作为自己的父容器)。有了这个parent上下文之后,再初始化自己持有的上下文(这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等)。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文定义的那些bean。

Bean加载过程

Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:

1)接口层描述了容器的重要组件及组件间的协作关系;

2)继承体系逐步实现组件的各项功能。

接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。

Spring组件按其所承担的角色可以划分为两类:

1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;

BeanDefinition:Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的后续操作直接从BeanDefinitionRegistry中读取配置信息。

2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。

InstantiationStrategy:负责实例化Bean操作,相当于Java语言中new的功能,并不会参与Bean属性的配置工作。属性填充工作留待BeanWrapper完成

BeanWrapper:继承了PropertyAccessor和PropertyEditorRegistry接口,BeanWrapperImpl内部封装了两类组件:

(1)被封装的目标Bean

(2)一套用于设置Bean属性的属性编辑器;

具有三重身份:(1)Bean包裹器(2)属性访问器 (3)属性编辑器注册表。PropertyAccessor:定义了各种访问Bean属性的方法。

PropertyEditorRegistry:属性编辑器的注册表。

总结:

  • Spring IOC容器主要有继承体系底层的BeanFactory、高层的ApplicationContext和WebApplicationContext
  • Bean有自己的生命周期
  • 容器启动原理:Spring应用的IOC容器通过tomcat的Servlet或Listener监听启动加载;Spring MVC的容器由DispatchServlet作为入口加载;Spring容器是Spring MVC容器的父容器。


以上就是Spring IOC控制反转通用原理总结了,后续内容会继续跟进。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表