Spring IOC的初始化过程——基于XML配置

Posted morewindows0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC的初始化过程——基于XML配置相关的知识,希望对你有一定的参考价值。

前言:在面试过程中,Spring IOC的初始化过程,基本上属于必答题,笔者的亲身经历。因此本文基于Spring的源码对其IOC的初始化过程进行总结。

注:spring版本为4.3.0。


1.调试前准备

在spring源码中,添加如下内容(有关spring源码如何导入idea,请查询相关资料):

技术分享图片

说明:

①User为简单bean,含有name和gender两个属性。

②User.xml为spring配置文件,仅仅对User进行简单的配置。

技术分享图片

③SpringIoCDebug为测试类。

技术分享图片

先看执行结果:

技术分享图片

执行成功,说明spring源码导入及相关配置成功。

2.调试过程

Spring IOC的初始化主线为读取xml文件——>解析xml文件——>注册到BeanFactory中,但是其整个脉络十分庞大,调试起来十分的繁琐。

#1.debug进入ClassPathXmlApplicationContext类的构造函数

技术分享图片

①super中parent为null,主要对AbstractApplicationContext中相关变量进行设置。

②setConfigLocations对xml文件资源进行设置。

③核心函数refresh(),实现了IOC的初始化。

#2.refresh源码(注refresh为AbstractApplicationContext中的函数,并且该函数为同步函数(synchronized代码块)

 1 public void refresh() throws BeansException, IllegalStateException {
 2         synchronized (this.startupShutdownMonitor) {
 3             // Prepare this context for refreshing.
 4             prepareRefresh();
 5 
 6             // Tell the subclass to refresh the internal bean factory.
 7             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 8 
 9             // Prepare the bean factory for use in this context.
10             prepareBeanFactory(beanFactory);
11 
12             try {
13                 // Allows post-processing of the bean factory in context subclasses.
14                 postProcessBeanFactory(beanFactory);
15 
16                 // Invoke factory processors registered as beans in the context.
17                 invokeBeanFactoryPostProcessors(beanFactory);
18 
19                 // Register bean processors that intercept bean creation.
20                 registerBeanPostProcessors(beanFactory);
21 
22                 // Initialize message source for this context.
23                 initMessageSource();
24 
25                 // Initialize event multicaster for this context.
26                 initApplicationEventMulticaster();
27 
28                 // Initialize other special beans in specific context subclasses.
29                 onRefresh();
30 
31                 // Check for listener beans and register them.
32                 registerListeners();
33 
34                 // Instantiate all remaining (non-lazy-init) singletons.
35                 finishBeanFactoryInitialization(beanFactory);
36 
37                 // Last step: publish corresponding event.
38                 finishRefresh();
39             }
40 
41             catch (BeansException ex) {
42                 if (logger.isWarnEnabled()) {
43                     logger.warn("Exception encountered during context initialization - " +
44                             "cancelling refresh attempt: " + ex);
45                 }
46 
47                 // Destroy already created singletons to avoid dangling resources.
48                 destroyBeans();
49 
50                 // Reset ‘active‘ flag.
51                 cancelRefresh(ex);
52 
53                 // Propagate exception to caller.
54                 throw ex;
55             }
56 
57             finally {
58                 // Reset common introspection caches in Spring‘s core, since we
59                 // might not ever need metadata for singleton beans anymore...
60                 resetCommonCaches();
61             }
62         }
63     }

①prepareRefresh()函数中主要设置开始时间和活动标志,this.active.set(true)

②核心代码:ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()BeanFactory的创建和Bean的解析都在该函数中进行。具体调试如下:

技术分享图片

进入refreshBeanFactory函数中可以发现,该方法属于AbstractRefreshableApplicationContext。首先会判断是否存在BeanFactory,如果存在则销毁当前Bean及BeanFactory。

技术分享图片

createBeanFactory()方法,创建BeanFactory,主要关注DefaultListableBeanFactory中的beanDefinitionMap成员变量,该变量用于后续存储bean相关数据。

接着设置beanFactory的序列号,是否需要定制化beanFactory。

关键函数loadBeanDefinitions该函数的参数为刚创建的beanFactory,进入该函数。

技术分享图片

首先根据当前的beanFactory创建XmlBeanDefinitionReader,接着对beanDefinitionReader,进行一些相关设置,如环境、资源导入器和实体解析器。我们进入关键的loadBeanDefinitions函数。

技术分享图片

由于我们使用的是配置文件位置类型,直接转入reader.loadBeanDefinitions中。

技术分享图片

loadBeanDefinitions函数返回类型为int,表示实际装载bean的个数。继续进入loadBeanDefinitions函数(在AbstractBeanDefinitionReader中对此函数进行了重载)。

技术分享图片

继续往下走:

技术分享图片

这里获取User.xml配置文件,并转换为Resource类型。继续往下走,又发现了loadBeanDefinitions函数,进入函数后会走到如下图所示的函数中。

技术分享图片

在XmlBeanDefinitionReader的loadBeanDefinitions中,进入核心函数

技术分享图片

技术分享图片

从上图中可以很清晰的看出,该函数的主要功能创建一个Document,并进行Bean的注册。在doLoadDocument函数中,对输入的资源进行了相应解析。

技术分享图片

接下来转入registerBeanDefinitions函数。

技术分享图片

首先会创建一个documentReader对象,利用该对象对doc对象进行注册。可以看到countBefore的值为0,表示目前在beanFactory中没有注册过bean。转入registerBeanDefinitions函数,会调用DefaultBeanDefinitionDocumentReader中。

技术分享图片

在doc中获取得到root。

技术分享图片

转入doRegisterBeanDefinitions中。

技术分享图片

通过委派的方式,首先设置bean的一些默认属性,如是否懒加载等。解析Bean的主要流程如下。

技术分享图片

技术分享图片

通过循环找到bean标签后,利用parseDefaultElement函数进行解析。

技术分享图片

技术分享图片

该函数会根据标签的不同类型进行对应解析,这里我们解析bean标签,转入该函数。

技术分享图片

这里是通过BeanDefinitionParserDelegate委派的形式来进行bean的解析。函数源码如下

 1 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
 2         String id = ele.getAttribute(ID_ATTRIBUTE); //获得bean标签中id
 3         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);//获取bean标签中name属性,如果存在
 4 
 5         List<String> aliases = new ArrayList<>();
 6         if (StringUtils.hasLength(nameAttr)) {
 7             String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
 8             aliases.addAll(Arrays.asList(nameArr));
 9         }
10         //bean别名检查
11         String beanName = id;
12         if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
13             beanName = aliases.remove(0);
14             if (logger.isDebugEnabled()) {
15                 logger.debug("No XML ‘id‘ specified - using ‘" + beanName +
16                         "‘ as bean name and " + aliases + " as aliases");
17             }
18         }
19         //检查beanName是否唯一
20         if (containingBean == null) {
21             checkNameUniqueness(beanName, aliases, ele);
22         }
23         //该函数中会对bean元素的相关属性进行解析。如id和class
24         AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
25         if (beanDefinition != null) {
26             if (!StringUtils.hasText(beanName)) {
27                 try {
28                     if (containingBean != null) {
29                         beanName = BeanDefinitionReaderUtils.generateBeanName(
30                                 beanDefinition, this.readerContext.getRegistry(), true);
31                     }
32                     else {
33                         beanName = this.readerContext.generateBeanName(beanDefinition);
34                         // Register an alias for the plain bean class name, if still possible,
35                         // if the generator returned the class name plus a suffix.
36                         // This is expected for Spring 1.2/2.0 backwards compatibility.
37                         String beanClassName = beanDefinition.getBeanClassName();
38                         if (beanClassName != null &&
39                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
40                                 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
41                             aliases.add(beanClassName);
42                         }
43                     }
44                     if (logger.isDebugEnabled()) {
45                         logger.debug("Neither XML ‘id‘ nor ‘name‘ specified - " +
46                                 "using generated bean name [" + beanName + "]");
47                     }
48                 }
49                 catch (Exception ex) {
50                     error(ex.getMessage(), ele);
51                     return null;
52                 }
53             }
54             String[] aliasesArray = StringUtils.toStringArray(aliases);
55             return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
56         }
57 
58         return null;
59     }

通过bean标签的解析创建AbstractBeanDefinition对象。

技术分享图片

最后生成BeanDefinitionHolder对象:bdHolder。

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);这里将类的属性值加载到bdHolder中。

技术分享图片

技术分享图片

最终注册实例,转入其函数内部,可以看到如下操作。

技术分享图片

到这里在最初的refresh函数中的obtainFreshBeanFactory方法基本上就算走完了,调试起来非常的繁杂。该方法中,既生成了beanFactory,又解析了xml文件,并将xml文件中的bean标签中的相关信息注册到beanFactory中。

技术分享图片

后续流程如下:

 1            //配置beanFactory,如环境、加载器等
prepareBeanFactory(beanFactory); 2 3 try { 4 // Allows post-processing of the bean factory in context subclasses.
//该函数体为空
5 postProcessBeanFactory(beanFactory); 6 7 // Invoke factory processors registered as beans in the context.

            //调用工厂处理器。 8 invokeBeanFactoryPostProcessors(beanFactory); 9 10 // Register bean processors that intercept bean creation.

          //注册bean的PostProcessor 11 registerBeanPostProcessors(beanFactory); 12 13 // Initialize message source for this context.

            //初始化信息源,主要和国际化相关 14 initMessageSource(); 15 16 // Initialize event multicaster for this context.

            //初始化事件传播器 17 initApplicationEventMulticaster(); 18 19 // Initialize other special beans in specific context subclasses.

            //函数体为空 20 onRefresh(); 21 22 // Check for listener beans and register them.

            //注册监听器 23 registerListeners(); 24 25 // Instantiate all remaining (non-lazy-init) singletons.

            //完成bean的初始化,该函数中就会创建bean,通过反射,进行bean的创建。 26 finishBeanFactoryInitialization(beanFactory); 27 28 // Last step: publish corresponding event.

            //最后完成整个ioc初始化,发布事件。 29 finishRefresh(); 30 }

至此,Spring IOC的基本流程走完,限于篇幅原因,将在下篇随笔中对IOC的初始化过程进行总结,请参看:Spring IOC的初始化过程——基于XML配置(二)


by Shawn Chen,2018.6.10日,下午。


相关内容

Spring IOC的初始化过程——基于XML配置(二)





















以上是关于Spring IOC的初始化过程——基于XML配置的主要内容,如果未能解决你的问题,请参考以下文章

初探Spring------Spring IOC:初始化过程---Resource定位

Spring IOC 相关配置-总结

Spring IOC基于XML容器的初始化(中)

Spring IOC基于XML容器的初始化(上)

Spring IOC基于XML容器的初始化(下)

基于Annotation的IOC 初始化