Java面试Spring源码分析 —— Spring IoC

Posted 醉恋学Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试Spring源码分析 —— Spring IoC相关的知识,希望对你有一定的参考价值。

 5、AbstractBeanDefinitionReader读取Bean定义资源

AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

可到org.springframework.beans.factory.support看一BeanDefinitionReader的结构:

在其抽象父类AbstractBeanDefinitionReader中定义了载入过程

//重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法  
public int loadBeanDefinitions(String location)
throws BeanDefinitionStoreException {
       return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources)
throws BeanDefinitionStoreException {
//获取在IoC容器初始化过程中设置的资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from
location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源
//加载多个指定位置的Bean定义资源文件 
Resource[] resources = ((ResourcePatternResolver) resourceLoader)
.getResources(location);             //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);  
if (actualResources != null) {              for (Resource resource : resources) {
actualResources.add(resource);             }             }                  if (logger.isDebugEnabled()) {                      logger.debug("Loaded " + loadCount + "
bean definitions from location pattern [" + location + "]");                  }                  return loadCount;              }              catch (IOException ex) {                  throw new BeanDefinitionStoreException(                          "Could not resolve bean definition
resource pattern [" + location + "]", ex);              }          }          else {              //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源  
//加载单个指定位置的Bean定义资源文件
Resource resource = resourceLoader.getResource(location);              //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能
int loadCount = loadBeanDefinitions(resource);              if (actualResources != null) {                  actualResources.add(resource);              }              if (logger.isDebugEnabled()) {                  logger.debug("Loaded " + loadCount + "
bean definitions from location [" + location + "]");            }              return loadCount;          }      }      //重载方法,调用loadBeanDefinitions(String);
public int loadBeanDefinitions(String... locations)
throws BeanDefinitionStoreException {          Assert.notNull(locations, "Location array must not be null");          int counter = 0;          for (String location : locations) {              counter += loadBeanDefinitions(location);          }          return counter;   }

loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

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

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

 

 

看到第8、16行,结合上面的ResourceLoader与ApplicationContext的继承关系图,可以知道此时调用的是DefaultResourceLoader中的getSource()方法定位Resource,因为FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类,所以此时又回到了FileSystemXmlApplicationContext中来。

6、资源加载器获取要读入的资源:

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

1    //获取Resource的具体实现方法  
2    public Resource getResource(String location) {   3        Assert.notNull(location, "Location must not be null");   4        //如果是类路径的方式,那需要使用ClassPathResource
//来得到bean 文件的资源对象  
5        if (location.startsWith(CLASSPATH_URL_PREFIX)) {   6            return new ClassPathResource(location.substring
(CLASSPATH_URL_PREFIX.length()), getClassLoader());   7        }   8         try {   9              // 如果是URL 方式,使用UrlResource 作为bean 文件的资源对象  
10             URL url = new URL(location);   11             return new UrlResource(url);   12            }   13            catch (MalformedURLException ex) { 14            } 15            //如果既不是classpath标识,又不是URL标识的Resource定位,则调用  
16            //容器本身的getResourceByPath方法获取Resource  
17            return getResourceByPath(location);   18            
19    }

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

protected Resource getResourceByPath(String path) {    
   if (path != null && path.startsWith("/")) {    
        path = path.substring(1);    
    }  
    //这里使用文件系统资源对象来定义bean 文件
    return new FileSystemResource(path);  
}


这样代码就回到了 FileSystemXmlApplicationContext 中来,他提供了FileSystemResource 来完成从文件系统得到配置文件的资源定义。

这样,就可以从文件系统路径上对IOC 配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在Spring 中我们看到它提供 的各种资源抽象,比如ClassPathResource, URLResource,FileSystemResource 等来供我们使用。上面我们看到的是定位Resource 的一个过程,而这只是加载过程的一部分.

7、XmlBeanDefinitionReader加载Bean定义资源:

Bean定义的Resource得到了,继续回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean文件的资源定义以后的载入过程。

1    //XmlBeanDefinitionReader加载资源的入口方法  
2    public int loadBeanDefinitions(Resource resource)
throws BeanDefinitionStoreException {   3        //将读入的XML资源进行特殊编码处理  
4        return loadBeanDefinitions(new EncodedResource(resource));   5    }     //这里是载入XML形式Bean定义资源文件方法
6    public int loadBeanDefinitions(EncodedResource encodedResource)
throws BeanDefinitionStoreException {     7    .......     8    try {     9       /将资源文件转为InputStream的IO流
10      InputStream inputStream = encodedResource.getResource().getInputStream();     11        try {     12           //从InputStream中得到XML的解析源    
13            InputSource inputSource = new InputSource(inputStream);     14            if (encodedResource.getEncoding() != null) {     15                inputSource.setEncoding(encodedResource.getEncoding());     16            }     17       //这里是具体的读取过程    
18       return doLoadBeanDefinitions(inputSource, encodedResource.getResource());     19     }     20        finally {     21            //关闭从Resource中得到的IO流    
22            inputStream.close();     23        }     24    }     25       .........     26}     27    //从特定XML文件中实际载入Bean定义资源的方法
28   protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)     29       throws BeanDefinitionStoreException {     30   try {     31       int validationMode = getValidationModeForResource(resource);     32       //将XML文件转换为DOM对象,解析过程由documentLoader实现    
33       Document doc = this.documentLoader.loadDocument(     34             inputSource, this.entityResolver, this.errorHandler,
validationMode, this.namespaceAware);     35      //这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则
36     return registerBeanDefinitions(doc, resource);     37    }     38    .......    
}

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

8、DocumentLoader将Bean定义资源转换为Document对象:

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

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 Java 1.4 with
Apache Crimson? " +   31                            "Upgrade to Apache Xerces (or Java 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对象过程完成。

接下来我们要继续分析Spring IoC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为Spring IoC管理的Bean对象并将其注册到容器中的。

以上是关于Java面试Spring源码分析 —— Spring IoC的主要内容,如果未能解决你的问题,请参考以下文章

Java面试Spring源码分析 —— Spring IoC

Java面试Spring源码分析 —— Spring IoC

Java面试Spring源码分析 —— Spring IoC

Spring源码分析专题——目录

详解Spring mvc工作原理及源码分析

【面试题解析】从 Vue 源码分析 key 的作用