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