IOC XMLBeanDefinitionReader
前言
1. Resource资源定位
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource ("beanFactoryTest.xml")
。
JavaSE中有个标准类 java.net.URL
,Spring为何选择自造轮子?
在JavaSE中有个标准类 java.net.URL
,该类为资源定位器(Uniform Resource Locator)。具体过程为:将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如“file:”“http:”“jar:”等。但是URL没有默认定义相对Classpath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如“classpath:”,但URL没有提供基本的方法。
java.net.URL
的局限性迫使 Spring 必须实现自己的资源加载策略:Resource接口封装底层资源。
Resource接口代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public interface Resource extends InputStreamSource { //资源是否存在,存在性 boolean exists(); //资源是否可读,可读性 default boolean isReadable() { return true; } //资源所代表的句柄是否被一个 stream 打开了,打开状态 default boolean isOpen() { return false; } //是否为 File default boolean isFile() { return false; } //返回资源的 URL 的句柄 URL getURL() throws IOException; //返回资源的 URI 的句柄 URI getURI() throws IOException; //返回资源的 File 的句柄 File getFile() throws IOException; //返回 ReadableByteChannel default ReadableByteChannel readableChannel() throws IOException { return java.nio.channels.Channels.newChannel(getInputStream()); } //资源内容的长度 long contentLength() throws IOException; //资源最后的修改时间 long lastModified() throws IOException; //根据当前资源创建一个相对资源 Resource createRelative(String relativePath) throws IOException; //资源的文件名 @Nullable String getFilename(); //资源的描述,可在错误处理中详细地打印出错的资源文件 String getDescription(); } |
---|
资源文件相关类图如下:
资源文件处理相关类图
- FileSystemResource :对
java.io.File
类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。 - ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
- UrlResource :对
java.net.URL
类型资源的封装。内部委派 URL 进行具体的资源操作。 - InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。
- ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
1.2 举例
如加载文件使用以下代码:
1 2 |
Resource resource=new ClassPathResource("beanFactoryTest.xml"); InputStream inputStream=resource.getInputStream(); |
---|
Resource接口对所有的资源文件进行统一处理。
ClassPathResource中的getInputStream,是通过class或者classLoader提供的底层方法进行调用。
1 2 3 4 5 6 7 8 9 |
if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } |
---|
先通过Resource相关类对配置文件进行封装和读取,之后交给XmlBeanDefinitionReader处理。
2 XmlBeanDefinitionReader
1.封装资源文件。当进入XmlBeanDefinitionReader后,首先对参数Resource使用EncodedResource类进行封装。
1 2 3 |
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } |
---|
2.获取输入流。从Resource中获取对应的InputStream并构造InputSource。
转入可复用方法loadBeanDefinitions(EncodedResource encodedResource)
。重点为调用函数doLoadBeanDefinitions(inputSource, encodedResource.getResource())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } //通过属性记录已经加载的资源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } //从encodedResource中获取已经封装的Resource对象,并再次从Resource中获取其中的inputStream try (InputStream inputStream = encodedResource.getResource().getInputStream()) { //注意:InputSource这个类并不来自于Spring,它的全路径是org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //**真正的核心部分** return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } } |
---|
- doLoadBeanDefinitions 主要做了三件事。
- 获取对XML文件的验证模式。
- 加载XML文件,并得到对应的Document。
- 根据返回的Document注册Bean信息。
代码如下:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//1. doLoadDocument 中有getValidationModeForResource获取XML文件的验证模式
//2. doLoadDocument 得到对应的Document
Document doc = doLoadDocument(inputSource, resource);
//3. 根据 Document 实例,注册 Bean 信息
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);
}
}
3 注册 BeanDefinition 流程 分析上一节的registerBeanDefinitions方法
//XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建 BeanDefinitionDocumentReader 对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 获取已注册的 BeanDefinition 数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载及注册bean,过程为:先创建 XmlReaderContext 对象,再注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 计算新注册的 BeanDefinition 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
3.2 parseBeanDefinitions方法分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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); } } |
---|
- 若节点为默认命名空间,调用
parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法,开启默认标签的解析注册过程 - 若节点为自定义命名空间,调用
parseCustomElement(Element ele)
方法,开启自定义标签的解析注册过程。
- Rafy 框架 - 流水号插件
- 产品前端重构(TypeScript、MVC框架设计)
- 寻找最优持仓期的开盘缺口盈利交易策略基于Matlab
- Android SlidingMenu 侧拉菜单的使用(详细配置)
- Rafy 框架 - 幽灵插件(假删除)
- 用粒子群优化算法求解旅行商问题
- 使用CNN(LSTM架构)进行序列预测基于TensorFlow
- 【独家】周志华教授gcForest(多粒度级联森林)算法预测股指期货涨跌
- 如何利用SOTER,1个版本内完成指纹支付开发?
- Rafy 框架 - 大批量导入实体
- Rafy 框架 - 执行SQL或存储过程
- 关于activitygroup过时,用frament替换操作
- Rafy 框架 - 为数据库生成注释
- CNN预测股票走势基于Tensorflow(思路+程序)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis案例:热key导致实例CPU 100%
- Python的包与模块导入问题
- 3分钟短文 | Laravel 自定义 SQL 查询参数绑定
- 宇智波程序笔记8- 解数独(Sudoku Solver)
- Angular如何响应DOM event
- Angular Template expression operators介绍
- k8s资源对象的升级、回滚、扩容、缩容
- emgucv之Matrix操作
- 使用 K8s 进行作业调度实战分享
- Kafka 常用运维脚本
- R语言进阶之Lattice绘图
- Scala守卫语句的集中用法
- SQL中的Null值处理
- SQL 获取上一个订单的状态
- Redis案例:Redis Cluster分片数据不均匀