IoC(Inversion of Control),即控制反转, 也可简单说为依赖注入(DI),旨在将对象间的依赖关系交由 外部环境去处理, 而不是由对象自己去获取,这将大大提升开发效率和降低程序复杂度,在Spring中,这个外部环境就是Spring IoC容器, 也是Spring生态的核心基础,除了Spring的IoC实现外, 其他还有诸如Guice这样的IoC实现, 本文将对Spring的IoC实现进行探讨一番。
-
IoC(Bean)容器的定义
Spring中IoC容器(Spring Bean容器)的表现形式比较常见的是BeanFactory和ApplicationContext, 而BeanFactory作为最基础的容器抽象,其规范了一个最基本的IoC容器应该具备的功能:
可以看到BeanFactory中仅定义了如何获取Bean的功能,
但一个Bean容器至少需要具备初始化Bean,创建Bean,装载Bean,注入Bean等基础功能,
这些功能分别在其他接口中进行了扩展,可从BeanFacotry的继承树中可知:
-
HierarchicalBeanFactory
HierarchicalBeanFactory定义了Bean容器之间的父子关系:
-
ListableBeanFactory
ListableBeanFactory丰富了获取Bean的功能,如获取多个Bean:
-
AutowireCapableBeanFactory
AutowireCapableBeanFactory定义了自动装配Bean的功能:
-
ConfigurableBeanFactory
ConfigurableBeanFactory定义了一些配置Bean容器的功能:
-
ApplicationContext
ApplicationContext作为最常用的Bean容器,比如WebApplicationContext,ClassPathXmlApplicationContext等,
其对最基础的BeanFactory进行了一些扩展,如EnvironmentCapable(容器运行时环境,有了它就能在Spring使用中区分开发,测试,生产等环境),MessageSource(消息参数化和国际化功能),ApplicationEventPublisher(应用事件发布功能),ResourcePatternResolver(资源解析功能),似乎ApplicationContext缺少了一些功能,如AutowireCapableBeanFactory(自动装配功能),但其实ApplicationContext通过内部包装了一个AutowireCapableBeanFactory来重用基础Bean容器的功能:
ApplicationContext作为开发人员最常用的Bean容器,Spring已经为我们提供很多现成的实现可用,
从ApplicationContext的继承树中可以看出:
其中有我们非常熟悉的ClassPathXmlApplicationContext和XmlWebApplicationContext。
-
Bean定义
既然有了Bean容器,那么Bean是什么呢?在Spring中,Bean的定义抽象为BeanDefinition:
-
Resource定义
在构建Bean容器时,我们会传入对应的配置文件路径,如:
对于传入的文件等资源信息,Spring内部会抽象为Resource接口:
-
Bean容器的实现
之前大概了解了Spring中的IoC容器体系结构,接下来将讲述IoC容器是如何实现,及一步步建立起来。可以从比较熟悉的一个IoC容器实现ClassPathXmlApplicationContext说起,开发应用时,ClassPathXmlApplicationContext基本用法:
上面2行代码,就已经构建了一个完整的IoC容器,现在需要逐步分析构建的各个主要过程。可以看下ClassPathXmlApplicationContext的继承树,来分析IoC功能是如何一步步建立起来的:
-
容器初始化过程
ClassPathXmlApplicationContext的构建从其构造函数中的refresh()开始,我们也可以通过该方法,在运行时对IoC容器进行重新加载:
对于configLocations,其中的文件路径是支持占位属性的,
如路径可以像这样classpath:${property_name}-context.xml,这其实又为我们提供了一种配置多环境的方式,refresh()方法涵盖了整个IoC容器初始化的过程:
整个refresh过程的调用链为:
可以看到ApplicationContext内部使用DefaultListableBeanFactory作为Bean容器实现,使用XmlBeanDefinitionReader读取Bean资源配置文件,使用BeanDefinitionParserDelegate对具体的Bean元素进行解析。
AbstractRefreshableApplicationContext.refreshBeanFactory():
再到XML文档读取器DefaultBeanDefinitionDocumentReader:
上面就是一个冗长的bean元素解析流程,完全解析后,回到BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());进行bean注册,最终将放到DefaultListableBeanFactory的beanDefinitionMap属性中:
-
Bean的依赖注入
当所有bean完成注册后,IoC容器还需要完成bean的依赖注入配置,对于lazyInit为true的bean,会在向容器getBean
时,实例化bean及完成依赖注入,整个依赖注入过程大概如:
从DefaultListableBeanFactory的getBean方法开始:
再看AbstractAutowireCapableBeanFactory.createBean:
以上就基本了解了IoC容器的依赖注入过程,基本思路虽然简单,将之前从配置文件中加载好的BeanDefinition进行逐一装配,
但其中涉及的思路和设计细节众多,完全弄透还是很需要时间来调试跟踪的。
-
Bean预实例化
对于平时使用Spring时,对于预实例化的bean,该动作会在refresh方法中的finishBeanFactoryInitialization中完成,内部其实也是一个getBean的过程。
-
注解式bean容器
在平常Spring使用中,通常并不会手动去配置bean之间的依赖关系,而是通过一些注解就能简单清晰地维护bean之间的依赖关系,这是如何做到的呢?首先我们需要开启spring的注解配置功能,需要如下配置如:
这需要先从xml命名空间(namespace)解析开始,该解析过程在DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()中:
而对于NamespaceHandler,会在XmlBeanDefinitionReader中初始化,并通过readerContext传递给BeanDefinitionParserDelegate:
在Spring源代码中,比如spring-beans和spring-context中配置的handlers:
因此context命名空间对应着ContextNamespaceHandler:
因此最终annotation-config将由AnnotationConfigBeanDefinitionParser来解析:
同理当解析到component-scan,则会调用ComponentScanBeanDefinitionParser:
通过component-scan(组件扫描)后,注解的类都被加载到IoC容器中,
除了BeanDefinition外,还有上面提到的注解处理器,
这些BeanPostProcessor也会如之前所说,在Bean实例化时调用:
比如针对@Autowried注解的处理器AutowiredAnnotationBeanPostProcessor:
以上则是依赖注入的基本实现轮廓,读者可以尝试debug各种情况。