《Spring源码深度解析》第二章 容器的基本实现
入门级别的spring配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="springHelloWorld" class="com.tutorial.spring.helloworld.impl.SpringHelloWorld"></bean> <bean id="strutsHelloWorld" class="com.tutorial.spring.helloworld.impl.StrutsHelloWorld"></bean> <bean id="helloWorldService" class="com.tutorial.spring.helloworld.HelloWorldService"> <property name="helloWorld"ref="springHelloWorld"/> </bean> </beans>
Spring容器的基本功能简要:
- 读取配置文件
- 解析出配置文件中的类,并进行实例化
- 获取实例化的对象,提供使用
对于以上第一步,读取配置文件,简单代码如下:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
在Java中,不同来源的资源被抽象成URL,这些资源具有不同的协议,如:"file:","http:","jar:"等,却没有"classpath:",所以Spring对要使用的资源文件进行了封装,相关接口如下:
public interface InputStreamSource { InputStream getInputStream() throws IOException; } public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return exists(); } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription(); }
以上ClassPathResource是Resource接口的实现,除此之外,还有其他的实现,如:FileSystemResource、UrlResource、InputStreamResource、ByteArrayResource等。通过这些Resource的实现类,在Spring中可以快速地定位和使用资源(getInputSteam就可以获取到对应资源的流)。
通过Resource的实现类获取到了Spring配置文件之后,传递给了XmlBeanFactory的构造函数:
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
从18行一直点击去,代码如下:
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); } public void ignoreDependencyInterface(Class<?> ifc) { this.ignoredDependencyInterfaces.add(ifc); }
可以发现,有三个类被添加到了ignoredDependencyInterfaces集合中,这个集合中的类不会被自动装配(?测试发现:注入A,A中有B属性,B实现了以上接口,B属性依然被自动注入了)
被添加的这三个类都是接口,用于bean感知spring的存在,java对象被spring容器创建并且管理期间,java对象无法感知这个过程,而如果这个对象类实现了以上三个接口,则可以获取到spring框架的相关信息,分别是:作为bean时的名称、bean容器的引用和加载这个bean的classLoader。
回到XmlBeanFactory的源码,对资源起加载作用的是第9行:
this.reader.loadBeanDefinitions(resource);
点进去,代码如下:
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
通过EncodedResource对资源进行再次包装,这里简单介绍一下这个EncodedResource类:
public class EncodedResource implements InputStreamSource { public EncodedResource(Resource resource) { this(resource, null, null); } private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) { super(); Assert.notNull(resource, "Resource must not be null"); this.resource = resource; this.encoding = encoding; this.charset = charset; } public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } } // ... 省略代码 }
这个类包装了资源的字符编码相关的操作。编码后的资源被这样使用:
InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); }
可见,资源被传递到了doLoadBeanDefinitions的另一个重载版本中(对异常的处理细度从高到低):
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
第5行将传入的资源转换为Document类型,方法内代码如下:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
这个方法中有两个注意点:1.getEntityResolver() 2.getValidationModeForResource()
getEntityResolver
XSD和DTD的区别。SAX在解析一个XML文档变为Document期间,会下载这个xml中的文档校验模式所对应的约束文件,并进行验证。下载是一个漫长的过程,而且可能下载失败,entityResolver为此提供解决方案,对象只需实现EntityResolver接口即可,在接口方法中,根据传入的SystemId和publicId,返回对应约束文件的InputSource即可。而约束文件分为两类:DTD和XSD,所以在Spring中,对EntityResolver的实现类中需要分情况处理约束文件,对于不同的类型,接口函数所接收到的参数存在一定规律:
可以看出,在EntityResolve的实现类中(实现函数中),只需要判断systemId参数的后缀即可:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
那以上是怎么根据这两个传入的参数找到对应约束文件的InputSource呢?在EntityResolver的实现类构造函数中,代码如下:
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) { this.dtdResolver = new BeansDtdResolver(); this.schemaResolver = new PluggableSchemaResolver(classLoader); }
以上两个对象也是实现了EntityResolver,也就是说,为了实现处理不同情况,创建了多个对象实现了相同的接口,其中一个实现类作为委托类,对情况进行判断后再调用其他实现类。可以看出,这个委托类就是上面的DelegateingEntityResolver,名字也符合语义。
1.BeanDtdResolver
进去处理第一件事就是判断SystemId是否以dtd结尾,其实在开始调用这个类之前,就已经进行过判断了,进去后又判断了一次,可以说是很严谨了。然后继续处理systemId,找到最后一个斜杠的索引,接着从这个索引后面开始搜索“spring-beans”这个字符串,如果没找到则返回null,SAX解析的时候就会从网络下载约束文件了。找到的话,就认为了约束文件的名字为:“spring-beans.dtd”(这里不明白为什么忽略了版本,如这个xml文件中的dtd指定为spring-beans-2.0.dtd),然后从spring工程的classpath中查找这个文件,然后返回对应文件的InputSource【查找和获取InputSource都是通过ClassPathResource工具类来处理】。
2.PluggableSchemaResolver
这里的处理比上面的情况稍微复杂一些。首先获取xsd的映射关系(url->文件名),使用懒加载方式,在锁住了this的情况下,再次对资源进行判空,防止竞态条件:
private Map<String, String> getSchemaMappings() { Map<String, String> schemaMappings = this.schemaMappings; if (schemaMappings == null) { synchronized (this) { schemaMappings = this.schemaMappings; if (schemaMappings == null) { if (logger.isTraceEnabled()) { logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]"); } try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader); if (logger.isTraceEnabled()) { logger.trace("Loaded schema mappings: " + mappings); } schemaMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings); this.schemaMappings = schemaMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex); } } } } return schemaMappings; }
以上所提到的schemaMappingsLocation默认值为:META-INF/spring.schemas,这个文件在spring-beans工程这里:
文件内容如下(纯粹就是一个 .properties 文件):
http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-4.2.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool-4.3.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool.xsd http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-4.1.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-4.2.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util-4.3.xsd=org/springframework/beans/factory/xml/spring-util.xsd http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util.xsd
加载完成后,转到到一个map中,最后返回。这个map中映射了url到对应xsd文件在工程中的路径关系,在当前EntityResolver的实现类中,获取到了这个map之后,再根据传递的systemId,通过这个map找到对应的xsd文件路径,最后再使用ClassPathResolver来加载资源,并返回。
以上两个实现类中,第二个稍微复杂一些,多了一个获取map的操作,之后加载资源的方式都一样,都是使用ClassPathResolver。而且所找的资源,都在这里:
好了,以上有了EntityResolver之后,就传递给DocumentBuilder即可(这个类存在于javax.xml.parsers中):
DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); }
从这段代码也可以推断出,EntityResolve并不是必须的,但最好设置一下,这样就不需要从网络下载文件了。
getValidationModeForResource
获取文档的校验模式(XSD或者DTD)。
while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); if (this.inComment || !StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { isDtdValidated = true; break; } if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
按行进行读取。文档中可能存在xml的注释,通过consumeCommentTokens进行处理,这个函数可以返回当前行的非注释部分。接着判断这部分中是否存在“DOCTYPE”这个字符串,如果是,则决定校验模式为DTD,否则是XSD。以上操作持续进行,直到遇见一个开标签“<”,因为文档的模式定义必定在标签开始之前。
确定好了验证模式之后,这个值会用在DocumentBuilderFactory的创建中。
总结一下解析xml为Document分三步走:
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource);
注册Bean
回顾一下函数 doLoadBeanDefinitions :
Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count;
前面章节中写了这么多,终于做好了一个工作:把配置文件转为Document。
接下来就开始根据得到的Document来注册bean了。进去首先判断根节点上的profiles属性,如果当前环境变量中并没有指定这个profile,则不解析这个beans标签了:
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; }
如果要解析的话,核心代码:
preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root);
第1、3个都是桩函数,里面函数体是空的,用来提高xml(Document)的扩展性,只需要创建一个子类来重写这两个桩函数即可在里面对文档做一些额外操作了。
第二个函数点进去,遍历根节点(beans标签是根节点)的子元素:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
判断子节点是否具有默认的命名空间:http://www.springframework.org/schema/beans。是则当做默认元素进行解析,不是则认为是自定义元素,因为两种标签的用法以及解析存在比较大差异,所以对两种情况分类解析。
默认元素: <bean id="test" class="test.TestBean" /> 自定义: <tx:annotation-driven/>
解析默认元素时,对四类子标签(import、alias、bean和beans)进行解析,对于beans,标签进行递归处理,重走上面的流程。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
对于自定义标签和默认标签的解析,放在第三章,本章完。
《Spring源码深度解析》第二章 容器的基本实现的更多相关文章
- spring源码深度解析— IOC 之 容器的基本实现
概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...
- spring源码深度解析— IOC 之 默认标签解析(上)
概述 接前两篇文章 spring源码深度解析—Spring的整体架构和环境搭建 和 spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...
- spring源码深度解析— IOC 之 开启 bean 的加载
概述 前面我们已经分析了spring对于xml配置文件的解析,将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中.至此,Spri ...
- Spring源码深度解析之Spring MVC
Spring源码深度解析之Spring MVC Spring框架提供了构建Web应用程序的全功能MVC模块.通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer ...
- Spring源码深度解析之事务
Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...
- Spring源码深度解析系列-----------org.springframework.aop-3.0.6.RELEASE
Spring源码深度解析系列-----------org.springframework.aop-3.0.6.RELEASE
- spring源码深度解析— IOC 之 默认标签解析(下)
在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...
- Spring源码深度解析之数据库连接JDBC
Spring源码深度解析之数据库连接JDBC JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供 ...
- spring源码深度解析—Spring的整体架构和环境搭建
概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的Java 开发框 ...
- Spring源码深度解析
Spring源码分析 Spring Web入门及源码学习 [Spring源码分析]Bean加载流程概览 Spring Framework 实现原理与源码解析系统 Spring源码分析--水门 Spri ...
随机推荐
- Mybatis源码解析(二)
根据上篇的代码跟踪mybatis已经ready好 SqlSessionFactory了,下面就是我们怎么去通过这个factory去获取sqlSession会话了,继续扒源码: mybatis-spri ...
- 旧版本linaro-ubuntu更改软件源
最近打算研究下arm版本的linaro ubuntu桌面系统,但是在安装软件时速度实在太慢,便想修改一下软件源. 无奈查看系统版本时,显示的是linaro 11.12,但却不知和ubuntu有和关系, ...
- AtCoder Beginner Contest 054 ABCD题
A - One Card Poker Time limit : 2sec / Memory limit : 256MB Score : 100 points Problem Statement Ali ...
- 洛谷P2470||bzoj1068 [SCOI2007]压缩
bzoj1068 洛谷P2470 区间dp入门题?只要注意到每个M“管辖”的区间互不相交即可 错误记录:有点小坑,比如aaaacaaaac最优解为aRRcR(意会坑在哪里),踩了一次 #include ...
- python tickle模块与json模块
#! /usr/bin/env python# -*- coding:utf-8 -*-#JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式 ...
- C8051F_CAN
CAN总线特点:基于报文编码而非对节点编码,增删节点对系统没有影响,靠干扰稳定性好,速率高. 小工具:CANtool 收发器:CAN总线收发器CTM1050,通信速率1Mbps,至少可连接110个节点 ...
- 通用全局CSS样式
PC全局样式 *{padding:0;margin:0;} div,dl,dt,dd,form,h1,h2,h3,h4,h5,h6,img,ol,ul,li,table,th,td,p,span,a{ ...
- TAIL and HEAD
TAIL and HEAD tail tail:将指定的文件的最后部分输出到标准设备,通常是终端,和cat以及more等显示文本的差别在于:假设该档案有更新,tail会自己主动刷新,确保你看到最新的档 ...
- 大数据freestyle: 共享单车轨迹数据助力城市合理规划自行车道
编者按:近年来,异军突起的共享单车极大地解决了人们共同面临的“最后一公里”难题,然而,共享单车发展迅猛,自行车道建设却始终没有能够跟上脚步.幸运的是摩拜单车大量的轨迹数据为我们提供了一种新的思路:利用 ...
- nmon各配置项含义介绍
1)nmon各配置项含义介绍