注意,看完这篇文章需要很长很长很长时间。。。
准备工作
本文会分析Spring的IOC模块的整体流程,分析过程需要使用一个简单的demo工程来启动Spring,demo工程我以备好,需要的童鞋自行在下方链接下载:
1
| https://github.com/shiyujun/spring-framework
|
Demo工程示例代码
本文源码分析基于Spring5.0.0,所以pom文件中引入5.0的依赖
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.0.RELEASE</version> </dependency> </dependencies>
|
然后写一个简单的接口和实现类
1 2 3 4 5 6 7 8 9
| public interface IOCService { public String hollo(); }
public class IOCServiceImpl implements IOCService { public String hollo() { return "Hello,IOC"; } }
|
新建一个application-ioc.xml
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">
<bean id="iocservice" class="cn.shiyujun.service.impl.IOCServiceImpl"/> </beans>
|
启动Spring
1 2 3 4 5 6 7
| public class IOCDemo { public static void main (String args[]){ ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application-ioc.xml"); IOCService iocService=context.getBean(IOCService.class); System.out.println(iocService.hollo()); } }
|
上方一个简单的demo工程相信各位童鞋在刚刚学习Spring的时候就已经玩的特别6了。我就不详细的说明了,直接开始看源码吧
ClassPathXmlApplicationContext
背景调查
在文章开始的demo工程中,我选择使用了一个xml文件来配置了接口和实现类之间的关系,然后使用了ClassPathXmlApplicationContext这个类来加载这个配置文件。现在我们就先来看一下这个类到底是个什么东东
首先看一下继承关系图(只保留了跟本文相关的,省略了很多其他的继承关系)
可以看到左下角的就是我们今天的主角ClassPathXmlApplicationContext、然后它的旁边是一个同门师兄弟FileSystemXmlApplicationContext。看名字就可以知道它们哥俩都是通过加载配置文件来启动Spring的,只不过一个是从程序内加载一个是从系统内加载。
除了这两个还有一个类AnnotationConfigApplicationContext比较值得我们关注,这个类是用来处理注解式编程的。
而最上边的ApplicationContext则是大名鼎鼎的Spring核心上下文了
源码分析
看一下这个类的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext { |
可以看到整体来看源码比较简单,只有setConfigLocations
和refresh
两个方法没有看到具体的实现。但是如果你因为这个而小巧了Spring那可就大错特错了,setConfigLocations
只是一个开胃小菜,refresh
才是我们本文的重点
setConfigLocations
setConfigLocations
方法的主要工作有两个:创建环境对象ConfigurableEnvironment和处理ClassPathXmlApplicationContext传入的字符串中的占位符
跟着setConfigLocations
方法一直往下走
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void setConfigLocations(String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { |
这里getEnironment()
就涉及到了创建环境变量相关的操作了
获取环境变量
1 2 3 4 5 6
| public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { this.environment = createEnvironment(); } return this.environment; }
|
看一下ConfigurableEnvironment
这个接口的继承图(1张没能截全,两张一块看)
这个接口比较重要的就是两部分内容了,一个是设置Spring的环境就是我们经常用的spring.profile配置。另外就是系统资源Property
接着看createEnvironment()
方法,发现它返回了一个StandardEnvironment
类,而这个类中的customizePropertySources
方法就会往资源列表中添加Java进程中的变量和系统的环境变量
1 2 3 4
| protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
|
处理占位符
再次回到 resolvePath
方法后跟进通过上方获取的ConfigurableEnvironment
接口的resolveRequiredPlaceholders
方法,终点就是下方的这个方法。这个方法主要就是处理所有使用${}方式的占位符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); |
refresh
配置文件名称解析完毕后,就到了最关键的一步refresh方法。这个方法,接下来会用超级长的篇幅来解析这个方法
先看一下这个方法里大致内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { |
是不是看着有点懵,不要着急,一行一行往下看,不研究明白誓不罢休
1. synchronized
为了避免refresh()
还没结束,再次发起启动或者销毁容器引起的冲突
2. prepareRefresh()
做一些准备工作,记录容器的启动时间、标记“已启动”状态、检查环境变量等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); this.closed.set(false); this.active.set(true);
if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); }
|
其中检查环境变量的核心方法为,简单来说就是如果存在环境变量的value为空的时候就抛异常,然后停止启动Spring
1 2 3 4 5 6 7 8 9 10 11
| public void validateRequiredProperties() { MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); for (String key : this.requiredProperties) { if (this.getProperty(key) == null) { ex.addMissingRequiredProperty(key); } } if (!ex.getMissingRequiredProperties().isEmpty()) { throw ex; } }
|
基于这个特性我们可以做一些扩展,提前在集合requiredProperties
中放入我们这个项目必须存在的一些环境变量。假说我们的生产环境数据库地址、用户名和密码都是使用环境变量的方式注入进去来代替测试环境的配置,那么就可以在这里添加这个校验,在程序刚启动的时候就能发现问题
3. obtainFreshBeanFactory()
乍一看这个方法也没几行代码,但是这个方法负责了BeanFactory的初始化、Bean的加载和注册等事件
1 2 3 4 5 6 7 8 9 10 11 12
| protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { |
BeanFactory
先看refreshBeanFactory()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| protected final void refreshBeanFactory() throws BeansException { |
这里一开始就实例化了一个DefaultListableBeanFactory,先看一下这个类的继承关系
可以看到这个哥们的背景相当大,所有关于容器的接口、抽象类他都继承了。再看他的方法
这方法简直多的吓人,妥妥的Spring家族超级富二代。看他的方法名称相信就可以猜出他大部分的功能了
BeanDefinition
在看loadBeanDefinitions()
这个方法之前,就必须了解一个东西了。那就是:BeanDefinition
我们知道BeanFactory是一个Bean容器,而BeanDefinition就是Bean的一种形式(它里面包含了Bean指向的类、是否单例、是否懒加载、Bean的依赖关系等相关的属性)。BeanFactory中就是保存的BeanDefinition。
看BeanDefinition的接口定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
|
读取配置文件
现在可以看loadBeanDefinitions()
方法了,这个方法会根据配置,加载各个 Bean,然后放到 BeanFactory 中
1 2 3 4 5 6 7 8 9 10 11 12
| @Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { |
1 2 3 4 5 6 7 8 9 10 11
| protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
|
第一个if是看有没有系统指定的配置文件,如果没有的话就走第二个if加载我们最开始传入的classpath:application-ioc.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; |
离解析越来越近了
这里先小小的看一下Spring中的设计模式,我们跟着loadBeanDefinitions()
方法往下走,最终会进入类XmlBeanDefinitionReader,这是因为我们这里要解析的配置文件是XML。如果我们使用Java类配置或者是Groovy的话就是另外的类了。看一下这个类继承图:
接着看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); }
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { |
下面是分为两步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { |
文件转换就不详细展开了,接着往下看
注册Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { |
改变bean定义的扩展点
preProcessXml和postProcessXml着两个办法是留给我们实现DefaultBeanDefinitionDocumentReader方法后自定义实现的
解析XML
接下来,看核心解析方法 parseBeanDefinitions()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
|
接着往下看这些标签的处理方式
default标签处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { |
简单看一下 标签的处理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
先从第一行往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null) |
创建BeanDefinition
接着是最重要的地方,如何根据配置创建 BeanDefinition 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); }
try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } |
终于终于这么长时间把这个BeanDefinition搞出来了,太不容易了!!!
接着回到刚才的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
Bean的注册
这次看注册bean的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName() |
又是一个长方法。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| |
到这里已经初始化了 Bean 容器,的配置也相应的转换为了一个个BeanDefinition,然后注册了所有的BeanDefinition到beanDefinitionMap
4. prepareBeanFactory()
现在回到最开始的refresh()
方法
prepareBeanFactory()
这个方法主要会设置BeanFactory的类加载器、添加几个 BeanPostProcessor、手动注册几个特殊的bean
继续撸代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 设置为加载当前ApplicationContext类的类加载器 beanFactory.setBeanClassLoader(getClassLoader());
// 设置 BeanExpressionResolver beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())) |
5. postProcessBeanFactory()
这个比较简单,又是Spring的一个扩展点
如果有Bean实现了BeanFactoryPostProcessor接口,
那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
6. invokeBeanFactoryPostProcessors()
调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
7. registerBeanPostProcessors()
又是一个扩展点
注册 BeanPostProcessor 的实现类,注意不是BeanFactoryPostProcessor
此接口有两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
分别会在Bean初始化之前和初始化之后得到执行
8. initMessageSource()
初始化当前 ApplicationContext 的 MessageSource,有想了解国际化的相关知识可以深入研究一下
9. initApplicationEventMulticaster()
这个方法主要为初始化当前 ApplicationContext 的事件广播器
撸代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private void initApplicationEventMulticaster() throws BeansException { |
10. onRefresh()
又是一个扩展点,子类可以在这里来搞事情
11. registerListeners()
注册事件监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| protected void registerListeners() { |
看到这里不要以为文章结束了,上方那么大的篇幅其实总结起来仅仅只介绍了Bean容器的创建过程,由于平台的字数限制所以本篇文章只能写到这里了。后续内容请看明天的下篇