Spring是一个非常优秀的开源框架,对于Java开发人员来说,很多时候我们的开发工作都是围绕着它来进行的。因此我们不应该仅仅局限于会使用它,更应该去学习他的工作原理,这样当遇到问题时可以更好的定位与解决。
今天的文章主要结合源码来讲解SpringIOC的原理,也是自己这段时间的学习总结,分享出来,也希望能够对大家有那么些许的帮助。
使用的Spring源码版本为5.2.2.RELEASE,大家可自行去github下载。
1 BeanFactory和ApplicationContext
在Spring的IOC容器中主要有BeanFactory和ApplicationContext两条路径,这两者的区别如下:
- BeanFactory是IOC容器的顶层接口,其定义了一个容器需要满足的最基本的功能。
- ApplicationContext继承自BeanFactory,对容器进行了扩展(资源发现、事件支持、本地化),是高级的容器。
BeanFactory的类图如下:
ApplicationContext的类图如下:
通过上面的类图我们能够发现,BeanFactory路径下的容器实现类为DefaultListableBeanFactory。ApplicationContext路径的实现有如下:
- ClassPathXmlApplicationContext 从classPath下加载配置
- FileSystemXmlApplicationContext 从文件系统加载配置
- XmlWebApplicationContext 加载配置至web容器
2 使用ClassPathXmlApplicationContext
我们以ClassPathXmlApplicationContext类的源码查看Spring IOC的初始化过程,在看源码之前我们先写一个demo,目录结构如下:
代码逻辑很简单,在application.xml中配置了一个User对象,然后创建ClassPathXmlApplicationContext并从中获取该对象,application.xml的内容如下:
1 | 复制代码<?xml version="1.0" encoding="UTF-8"?> |
使用debug查看,我们可以看见创建后的容器对象如下:
通过上面的截图我们可以看到ClassPathXmlApplicationContext中的BeanFactory是一个DefaultListableBeanFactory对象,在该容器对象中有一个为beanDefinitionMap的ConcurrentHashMap实例,这个Map就是用来存储对象信息的,在BeanDefinition中存储了bean的描述,如下:
通过上面的例子我们能够看到我们指定一个xml的路径,即可在在创建好的容器容器中找到对该配置文件中配置的对象的描述信息,这便是Spring IOC需要完成的工作,概括如下:
- 资源的定位
- 将资源中的数据解析为一个个的BeanDefinition对象
- 将这些BeanDefinition对象注册到一个Map中
3 源码解析
了解了Spring IOC容器初始化所完成的事情后,我们正式开始查看源码,通过源码来详细的了解这三个过程都是怎样实现的。
创建容器时,最终调用的ClassPathXmlApplicationContext构造方法如下所示,容器的初始化逻辑在调用的refresh()方法中实现
1 | 复制代码public ClassPathXmlApplicationContext( |
refresh()方法的定义在AbstractApplicationContext类中,其源码如下:
1 | 复制代码public void refresh() throws BeansException, IllegalStateException { |
IOC容器的初始化逻辑是在obtainFreshBeanFactory()方法中定义的,我们一步步点击进去,会进入AbstractRefreshableApplicationContext类中的refreshBeanFactory()方法,该方法的处理逻辑如下:
1 | 复制代码@Override |
上面代码的主要逻辑在loadBeanDefinitions(beanFactory)中,点进去会发现这个方法是调用的AbstractXmlApplicationContext类中的方法,源码如下
1 | 复制代码@Override |
不知大家是否还记得在我们创建ClassPathXmlApplicationContext对象的逻辑中,会调用一个setConfigLocations的方法。在上面的方法中会使用这个值进行资源的定位加载,我们点进去上面第二个if中调用的loadBeanDefinitions方法,会发现这是AbstractBeanDefinitionReader中的一个方法,其源码如下:
1 | 复制代码@Override |
上面这段代码的逻辑是通过路径定位资源,并将资源转换为Resource对象,之后会调用BeanDefinitionReader中的loadBeanDefinitions(Resource resource)方法,我们使用的是XmlBeanDefinitionReader对象,继续点进去查看,其源码如下:
1 | 复制代码public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { |
在上面的代码中我们会发现在这里会调用一个doLoadBeanDefinitions的方法,这是真正处理加载BeanDefinitions的逻辑,其源码如下
1 | 复制代码protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) |
从上面的代码可以发现,在Spring中是使用Document对象来解析配置的内容并进行BeanDefinition的注册工作,registerBeanDefinitions方法的源码如下
1 | 复制代码@Override |
上面的代码是解析解析文档中的标签,这里我们查看下解析bean标签的逻辑,processBeanDefinition方法,源码如下
1 | 复制代码protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
最终会调用DefaultListableBeanFactory中的registerBeanDefinition方法,将BeanDefinition注册到容器中,使用容器中的beanDefinitionMap参数进行存储,这是一个ConcurrentHashMap对象,使用对象名称作为key,BeanDefinition作为值,可以跟我们先前例子的debug的结果是一致的。
1 | 复制代码@Override |
至此使用ClassPathXmlApplicationContext容器的初始化源码就分析结束了。写的比较粗糙,也就是简单的梳理了下容器的初始化流程,具体的内容需要大家去下载源码一步步的进行查看。
4 简单的总结
BeanFactory和ApplicationContext的关系
- BeanFactory是容器的顶层接口,定义了容器应该具备的基本功能。
- ApplicationContext继承自BeanFactory,对BeanFactory进行了扩展,是更高级的容器。
IOC的初始化过程
1、BeanDefinition资源的定位与加载(加载为Resource对象)
2、BeanDefinition的解析,使用的是(Document相关API)
3、BeanDefinition的注册,存入一个叫beanDefinitionMap的ConcurrentHashMap的对象。
Spring的IOC容器中是使用BeanDefinition来进行定义的,来自各个地方的资源,Spring都会将其转换为BeanDefinition。
本文转载自: 掘金