zoukankan      html  css  js  c++  java
  • Spring技术内幕_IOC容器载入Bean定义资源文件

    转自:http://blog.csdn.net/chjttony/article/details/6259723

    1.当spring的IoC容器将Bean定义的资源文件封装为Spring的Resource之后,接下来要做的就是通过Spring的资源加载器(resourceLoader)读入Bean定义资源文件的过程。对于IoC容器来说,Bean定义的载入过程就是将Bean定义资源文件读入进内存并解析转换成Spring所管理的Bean的数据结构的过程。相对于SpringIoC容器定位Bean定义资源文件来说,Bean定义资源文件的载入和解析过程更复杂一些,因此按照程序的运行步骤逐条分析其实现原理。

    2.以FileSystemXmlApplicationContext为例分析其载入和解析Bean定义资源文件的过程:

    首先从FileSystemXmlApplicationContext的入口构造函数分析起,其代码如下:

    [java] view plain copy
     
    1. //FileSystemXmlApplicationContext IoC容器进行初始化的入口构造函数  
    2.     public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  
    3.             throws BeansException {  
    4. //调用父类构造方法,为容器设置资源加载器(resourceLoader)  
    5.         super(parent);  
    6. //调用父类AbstractRefreshableConfigApplicationContext的方法,设置//Bean定义的资源文件,完成IoC容器Bean定义资源的定位  
    7.         setConfigLocations(configLocations);  
    8.         if (refresh) {  
    9. //调用父类AbstractApplicationContext的refresh()  
    10. //函数启动载入Bean定义的过程,是Ioc容器载入Bean定义的入口  
    11.             refresh();  
    12.         }  
    13.     }  

    Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个IoC容器对Bean定义的载入过程。

    3.AbstractApplicationContext的refresh函数载入Bean定义过程:

    [java] view plain copy
     
    1. public void refresh() throws BeansException, IllegalStateException {  
    2.         synchronized (this.startupShutdownMonitor) {  
    3.             //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识  
    4.             prepareRefresh();  
    5.             //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从  
    6. //子类的refreshBeanFactory()方法启动  
    7.             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  
    8. //为BeanFactory配置容器特性,例如类加载器、事件处理器等  
    9.             prepareBeanFactory(beanFactory);  
    10.             try {  
    11.                 //为容器的某些子类指定特殊的BeanPost事件处理器  
    12.                 postProcessBeanFactory(beanFactory);  
    13.                 //调用所有注册的BeanFactoryPostProcessor的Bean  
    14.                 invokeBeanFactoryPostProcessors(beanFactory);  
    15.                 //为BeanFactory注册BeanPost事件处理器.  
    16.                 //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件  
    17.                 registerBeanPostProcessors(beanFactory);  
    18.                 //初始化信息源,和国际化相关.  
    19.                 initMessageSource();  
    20.                 //初始化容器事件传播器.  
    21.                 initApplicationEventMulticaster();  
    22.                 //调用子类的某些特殊Bean初始化方法  
    23.                 onRefresh();  
    24.                 //为事件传播器注册事件监听器.  
    25.                 registerListeners();  
    26.                 //初始化所有剩余的单态Bean.  
    27.                 finishBeanFactoryInitialization(beanFactory);  
    28.                 //初始化容器的生命周期事件处理器,并发布容器的生命周期事件  
    29.                 finishRefresh();  
    30.             }  
    31.             catch (BeansException ex) {  
    32.                 //销毁以创建的单态Bean  
    33.                 destroyBeans();  
    34. //取消refresh操作,重置容器的同步标识.  
    35.                 cancelRefresh(ex);  
    36.                 throw ex;  
    37.             }  
    38.         }  
    39.     }  

    refresh()方法主要为IoC容器Bean的生命周期管理提供条件,Spring IoC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

    AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程,代码如下:

     

    [java] view plain copy
     
    1. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {  
    2. //这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具//体实现调用子类容器的refreshBeanFactory()方法          
    3. refreshBeanFactory();  
    4.         ConfigurableListableBeanFactory beanFactory = getBeanFactory();  
    5.         if (logger.isDebugEnabled()) {  
    6.             logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);  
    7.         }  
    8.         return beanFactory;  
    9.     }  

     

     

    4.AbstractApplicationContext子类的refreshBeanFactory()方法:

    AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法,方法的源码如下:

    [java] view plain copy
     
    1. protected final void refreshBeanFactory() throws BeansException {  
    2.         if (hasBeanFactory()) {//如果已经有容器,销毁容器中的bean,关闭容器  
    3.             destroyBeans();  
    4.             closeBeanFactory();  
    5.         }  
    6.         try {  
    7.             //创建IoC容器  
    8.             DefaultListableBeanFactory beanFactory = createBeanFactory();  
    9.             beanFactory.setSerializationId(getId());  
    10. //对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等  
    11.             customizeBeanFactory(beanFactory);  
    12. //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定//义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器  
    13.             loadBeanDefinitions(beanFactory);  
    14.             synchronized (this.beanFactoryMonitor) {  
    15.                 this.beanFactory = beanFactory;  
    16.             }  
    17.         }  
    18.         catch (IOException ex) {  
    19.             throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);  
    20.         }  
    21.     }  

    refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

    和refreshBeanFactory方法类似,载入Bean定义的方法loadBeanDefinitions也使用了委派模式,在AbstractRefreshableApplicationContext类中只定义了抽象方法,具体的实现调用子类容器中的方法实现。

    5.AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:

    AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:

    [java] view plain copy
     
    1. public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {  
    2. ……  
    3. //实现父类抽象的载入Bean定义方法  
    4.     @Override  
    5.     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  
    6.         //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器       //中去,容器使用该读取器读取Bean定义资源  
    7.         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
    8. //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的  
    9. //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因//此,容器本身也是一个资源加载器  
    10.         beanDefinitionReader.setResourceLoader(this);  
    11.         //为Bean读取器设置SAX xml解析器  
    12.         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
    13.         //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制  
    14.         initBeanDefinitionReader(beanDefinitionReader);  
    15.         //Bean读取器真正实现加载的方法  
    16.         loadBeanDefinitions(beanDefinitionReader);  
    17.     }  
    18.     //Xml Bean读取器加载Bean定义资源  
    19.     protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
    20.         //获取Bean定义资源的定位  
    21.         Resource[] configResources = getConfigResources();  
    22.         if (configResources != null) {  
    23.             //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位  
    24. //的Bean定义资源  
    25.             reader.loadBeanDefinitions(configResources);  
    26.         }  
    27. //如果子类中获取的Bean定义资源定位为空,则获取//FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源  
    28.         String[] configLocations = getConfigLocations();  
    29.         if (configLocations != null) {  
    30.             //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位  
    31. //的Bean定义资源  
    32.             reader.loadBeanDefinitions(configLocations);  
    33.         }  
    34.     }  
    35.     //这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法  
    36.     //该方法在ClassPathXmlApplicationContext中进行实现,对于我们  
    37.     //举例分析源码的FileSystemXmlApplicationContext没有使用该方法  
    38. protected Resource[] getConfigResources() {  
    39.         return null;  
    40.     }   ……  
    41. }  

    Xml Bean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法读取Bean定义资源。

    由于我们使用FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

    6. AbstractBeanDefinitionReader读取Bean定义资源:

    AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

    [java] view plain copy
     
    1. //重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法  
    2.     public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  
    3.         return loadBeanDefinitions(location, null);  
    4.     }  
    5. public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {  
    6.         //获取在IoC容器初始化过程中设置的资源加载器  
    7.         ResourceLoader resourceLoader = getResourceLoader();  
    8.         if (resourceLoader == null) {  
    9.             throw new BeanDefinitionStoreException(  
    10.                     "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");  
    11.         }  
    12.         if (resourceLoader instanceof ResourcePatternResolver) {  
    13.             try {  
    14.                 //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源  
    15.                 //加载多个指定位置的Bean定义资源文件  
    16.                 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);  
    17.                 //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能  
    18.                 int loadCount = loadBeanDefinitions(resources);  
    19.                 if (actualResources != null) {  
    20.                     for (Resource resource : resources) {  
    21.                         actualResources.add(resource);  
    22.                     }  
    23.                 }  
    24.                 if (logger.isDebugEnabled()) {  
    25.                     logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");  
    26.                 }  
    27.                 return loadCount;  
    28.             }  
    29.             catch (IOException ex) {  
    30.                 throw new BeanDefinitionStoreException(  
    31.                         "Could not resolve bean definition resource pattern [" + location + "]", ex);  
    32.             }  
    33.         }  
    34.         else {  
    35.             //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源  
    36.             //加载单个指定位置的Bean定义资源文件  
    37.             Resource resource = resourceLoader.getResource(location);  
    38.             //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能  
    39.             int loadCount = loadBeanDefinitions(resource);  
    40.             if (actualResources != null) {  
    41.                 actualResources.add(resource);  
    42.             }  
    43.             if (logger.isDebugEnabled()) {  
    44.                 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");  
    45.             }  
    46.             return loadCount;  
    47.         }  
    48.     }  
    49.     //重载方法,调用loadBeanDefinitions(String);  
    50.     public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {  
    51.         Assert.notNull(locations, "Location array must not be null");  
    52.         int counter = 0;  
    53.         for (String location : locations) {  
    54.             counter += loadBeanDefinitions(location);  
    55.         }  
    56.         return counter;  
    57.     }  

    loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。

    从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

    首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。

    其次,真正执行加载功能是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法。

    7.资源加载器获取要读入的资源:

    XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源,其源码如下:

    [java] view plain copy
     
    1. //获取Resource的具体实现方法  
    2. public Resource getResource(String location) {  
    3.         Assert.notNull(location, "Location must not be null");  
    4.         //这里除了带有classpath标识的Resource  
    5.         if (location.startsWith(CLASSPATH_URL_PREFIX)) {  
    6.             return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());  
    7.         }  
    8.         else {  
    9.             try {  
    10.                 //这里处理URL标识的Resource定位  
    11.                 URL url = new URL(location);  
    12.                 return new UrlResource(url);  
    13.             }  
    14.             catch (MalformedURLException ex) {  
    15.                 //如果既不是classpath标识,又不是URL标识的Resource定位,则调用  
    16.                 //容器本身的getResourceByPath方法获取Resource  
    17.                 return getResourceByPath(location);  
    18.             }  
    19.         }  
    20.     }  

    FileSystemXmlApplicationContext容器提供了getResourceByPath方法的实现,就是为了处理既不是classpath标识,又不是URL标识的Resource定位这种情况。

    现在,Bean定义的Resource得到了,下面我们继续跟随程序执行方向,分析XmlBeanDefinitionReader的loadBeanDefinitions方法。

    8. XmlBeanDefinitionReader加载Bean定义资源:

    [java] view plain copy
     
    1. //XmlBeanDefinitionReader加载资源的入口方法  
    2. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {  
    3.         //将读入的XML资源进行特殊编码处理  
    4.         return loadBeanDefinitions(new EncodedResource(resource));  
    5.     }  
    6. //这里是载入XML形式Bean定义资源文件方法  
    7.     public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {  
    8.         Assert.notNull(encodedResource, "EncodedResource must not be null");  
    9.         if (logger.isInfoEnabled()) {  
    10.             logger.info("Loading XML bean definitions from " + encodedResource.getResource());  
    11.         }  
    12.         //这里是获取线程局部变量  
    13.         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();  
    14.         if (currentResources == null) {  
    15.             currentResources = new HashSet<EncodedResource>(4);  
    16.             this.resourcesCurrentlyBeingLoaded.set(currentResources);  
    17.         }  
    18.         if (!currentResources.add(encodedResource)) {  
    19.             throw new BeanDefinitionStoreException(  
    20.                     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");  
    21.         }  
    22.         try {  
    23.             //将资源文件转换为IO输入流  
    24.             InputStream inputStream = encodedResource.getResource().getInputStream();  
    25.             try {  
    26.                 InputSource inputSource = new InputSource(inputStream);  
    27.                 if (encodedResource.getEncoding() != null) {  
    28.                     inputSource.setEncoding(encodedResource.getEncoding());  
    29.                 }  
    30.                 //具体读取过程的方法  
    31.                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());  
    32.             }  
    33.             finally {  
    34.                 inputStream.close();  
    35.             }  
    36.         }  
    37.         catch (IOException ex) {  
    38.             throw new BeanDefinitionStoreException(  
    39.                     "IOException parsing XML document from " + encodedResource.getResource(), ex);  
    40.         }  
    41.         finally {  
    42.             currentResources.remove(encodedResource);  
    43.             if (currentResources.isEmpty()) {  
    44.                 this.resourcesCurrentlyBeingLoaded.remove();  
    45.             }  
    46.         }  
    47.     }  
    48. //从特定XML文件中实际载入Bean定义资源的方法  
    49. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
    50.             throws BeanDefinitionStoreException {  
    51.         try {  
    52.             int validationMode = getValidationModeForResource(resource);  
    53.             //将XML文件转换为DOM对象,解析过程由documentLoader实现  
    54.             Document doc = this.documentLoader.loadDocument(  
    55.                     inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());  
    56.             //这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean  
    57.             //配置规则  
    58.             return registerBeanDefinitions(doc, resource);  
    59.         }  
    60.         catch (BeanDefinitionStoreException ex) {  
    61.             throw ex;  
    62.         }  
    63.         catch (SAXParseException ex) {  
    64.             throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
    65.                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);  
    66.         }  
    67.         catch (SAXException ex) {  
    68.             throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
    69.                     "XML document from " + resource + " is invalid", ex);  
    70.         }  
    71.         catch (ParserConfigurationException ex) {  
    72.             throw new BeanDefinitionStoreException(resource.getDescription(),  
    73.                     "Parser configuration exception parsing XML from " + resource, ex);  
    74.         }  
    75.         catch (IOException ex) {  
    76.             throw new BeanDefinitionStoreException(resource.getDescription(),  
    77.                     "IOException parsing XML document from " + resource, ex);  
    78.         }  
    79.         catch (Throwable ex) {  
    80.             throw new BeanDefinitionStoreException(resource.getDescription(),  
    81.                     "Unexpected exception parsing XML document from " + resource, ex);  
    82.         }  
    83.     }  

     通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现。

    9. DocumentLoader将Bean定义资源转换为Document对象:

    DocumentLoader将Bean定义资源转换成Document对象的源码如下:

    [java] view plain copy
     
    1. //使用标准的JAXP将载入的Bean定义资源转换成document对象  
    2. public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,  
    3.             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {  
    4.         //创建文件解析器工厂  
    5.         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
    6.         if (logger.isDebugEnabled()) {  
    7.             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");  
    8.         }  
    9.         //创建文档解析器  
    10.         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  
    11.         //解析Spring的Bean定义资源  
    12.         return builder.parse(inputSource);  
    13.     }  
    14. protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)  
    15.             throws ParserConfigurationException {  
    16.         //创建文档解析工厂  
    17.         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
    18.         factory.setNamespaceAware(namespaceAware);  
    19.         //设置解析XML的校验  
    20.         if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {  
    21.             factory.setValidating(true);  
    22.             if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {  
    23.                 factory.setNamespaceAware(true);  
    24.                 try {  
    25.                     factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);  
    26.                 }  
    27.                 catch (IllegalArgumentException ex) {  
    28.                     ParserConfigurationException pcex = new ParserConfigurationException(  
    29.                             "Unable to validate using XSD: Your JAXP provider [" + factory +  
    30.                             "] does not support XML Schema. Are you running on <a href="http://lib.csdn.net/base/java" class='replace_word' title="Java 知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>1.4 with Apache Crimson? " +  
    31.                             "Upgrade to Apache Xerces (or <a href="http://lib.csdn.net/base/javaee" class='replace_word' title="Java EE知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java</a> 1.5) for full XSD support.");  
    32.                     pcex.initCause(ex);  
    33.                     throw pcex;  
    34.                 }  
    35.             }  
    36.         }  
    37.         return factory;  
    38.     }  

    该解析过程调用JavaEE标准的JAXP标准进行处理。

    至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。

    10.看源代码的个人心得总结:

    通过这几天看源码,个人总结一些心得:代码毕竟不是文章,不能从头到尾详细看,个人觉得看源码比较好的方法是:

    首先,先实现一个简单例子调用源码,让整个应用能简单跑起来。

    然后,在进入代码最开始的地方打一个调试断点,使用Debug工具进行单步调试,直到跑完所有流程。

    通过调试就可以理解整个代码的工作流程和调用顺序,有助于理清思路,理解其大概的设计思想。

    Spring代码的确实比较复杂,代码中大量使用了设计模式,另外为了解耦合,代码的分工比较明确,对象也非常的多,对于没有分析源码经验的人来说,阅读代码发现其跨度和跳转非常大,难度和挑战比较大。

    我也是第一次分析Spring源码,希望和大家一起学习探讨。

  • 相关阅读:
    如何根据关键字匹配度排序
    LeetCode 题解目录
    Spring Boot、Cloucd 学习示例
    JavaScript工具库
    使用 Docker 部署 Spring Boot 项目
    LeetCode 寻找两个有序数组的中位数
    Bean 生命周期
    Dubbo支持的协议
    MySQL组成模块
    Spring Boot 搭建TCP Server
  • 原文地址:https://www.cnblogs.com/heyanan/p/6086610.html
Copyright © 2011-2022 走看看