Ioc容器是Spring的核心,Spring的依赖反转由Ioc实现,同时,几乎其他所有的Spring特性都依赖Ioc容器。Ioc容器是Spring框架最核心的部分。
先说一段容器启动的Demo代码:
public class IocDemo {
static AnnotationConfigApplicationContext annotationConfigApplicationContext;
public static void main(String[] args) throws InterruptedException {
annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.scan("com.tubemogul.springsecuredemo.springDemo.bean");
annotationConfigApplicationContext.refresh();
TestService2 testService2 = annotationConfigApplicationContext.getBean(TestService2.class);
System.out.println(testService2.getClass().getName());
}
}
这里选择的容器是AnnotationConfigApplicationContext,基于注解配置的(个人认为比XML好,依赖配置和代码在一起,一可以减少配置量,二也更直观)。在获取容器对象前有3步:
1. new出AnnotationConfigApplicationContext对象
2. 调用scan函数,扫描bean配置。
3. refresh完成容器初始化。
首先理解AnnotationConfigApplicationContext对象,除了这个以外,类似的还有XmlConfigApplicationContext对象等。Spring容器有很多种,应对不同的需求场景,容器的功能按接口进行逻辑拆分。这是Ioc容器接口设计图:
理解下几个顶层接口,BeanFactory接口包含对bean的操作,比如获取bean,Message接口包含消息类操作,ReasourceLoader负责资源加载,ApplicationEventPublisher包含应用程序事件发布功能。
这里可看出ioc容器的2大系列,beanFactory系列和applicationContext系列的关系,实际applicationContext系列容器是beanFactory容器系列的功能增强版,除了容器管理功能外,还增加了消息,资源加载,应用程序事件发布等功能。
那么看看我用的实现类AnnotationConfigApplicationContext的继承关系:
重点理解BeanFactory的功能,沿着继承树查看,getBean()方法是在AbstractApplicationContext类中实现:
这里调用getBeanFactory()获取BeanFactory,真实的BeanFactory封装在GenericApplicationContext,本质上是一个DefaultListableBeanFactory:
进入DefaultListableBeanFactory查看,这里就是实际持有Bean和BeanDefinition的地方
追踪annotationConfigApplicationContext.scan(“com.tubemogul.springsecuredemo.springDemo.bean”);方法,我们会来到ClassPathBeanDefinitionScanner类中的doScan方法:
从注释和代码上理解,这个方法就是根据入参的包地址扫描配置,并装配成一个BeanDefinition的集合,最后注册在registry中。AnnotationConfigApplicationContext是从代码注解中扫描配置装配出BeanDefinition(同理XmlConfigApplicationContext就是从XML文件中扫描装配出BeanDefinition)。看下BeanDefinition接口:
基本上涵盖了生成一个Bean需要的函数,可以理解Spring容器生成Bean就是基于BeanDefinition来生成的。
再说说最后的注册BeanDefinition到register对象中,这个register对象实际上就是AnnotationConfigApplicationContext,这类是实现了AnnotationConfigRegistry接口的
追踪register方法,其实是在AnnotationConfigApplicationContext持有的AnnotatedBeanDefinitionReader对象中实现的,这应该算是设计模式中的桥接模式。
这个代码其实写得有点绕,看AnnotatedBeanDefinitionReader的初始化:
看到没有,这里又把AnnotationConfigApplicationContext当成register给传进去了,追踪到AnnotatedBeanDefinitionReader里的regiseterBean方法:
最终调用了registry.registerBeanDefinition()方法完成了注册BeanDefinition的过程:
理解registry之前,理解一下为什么Spring作者要弄一个AnnotatedBeanDefinitionReader出来,我理解就是针对AnnotationConfig的一些专用处理的封装,在给真正registry注册BeanDefition之前,抽象了一层Reader,适配不同的配置源。
registerBeanDefinition其实是在DefaultListableBeanFactory中实现的,最终BeanDefinition是在DefaultListableBeanFactory的beanDefinitionMap中保存。至此,Scan()函数的工作就完成了,其实就是读取配置,解析配置,转换为BeanDefinition并保存。
AbstractApplicationContext.refresh()方法是在AbstractApplicationContext类中实现,直接看源码:
步骤很多,根据函数名和注释大致可以理解每一步是做什么的
调用过refresh函数后,就可以从容器中获取对象了。
最后可以看出来,其实读取配置的过程就是装配BeanDefinition并保存,在getBean的时候才会根据BeanDefinition生成时间Bean对象,当然如果是单例对象就会被缓存起来,生成对象时会连带注入依赖对象等等操作。
功能有多复杂,代码就有多复杂,Spring ioc细节很多,也不可能一一搞清楚,只有先了解大致原理,在具体使用中在根据需要选择性的深入