天天看点

Spring源码深度解析第1部分 核心实现

第1部分 核心实现

第一章 Spring整体架构和环境搭建

1.1 Spring的整体架构

1. Spring框架是一个分层架构,它包含一系列的功能要素,并被分成大约20个模块。这些模块被总结为以下几部分。
	1. Core Contrainer
		1. Core Contrainer(核心容器)包含有Core、Beans、Context和Expression Language模块。
		2. Core和Beans模块是框架的基础部分,提供IoC(转控制)和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序单例模式的需求,并真正允许你从程序逻辑中分离出依赖关系和配置。
			1. Core模块主要包含Spring框架基本的核心工具类,Spring的其它组件都要用到这个包里的类,Core模块是其它组件的基本核心。当然你也可以在自己的应用系统中使用这些工具类。
			2. Beans模块是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control/Dependency Injection(IoC/DI)操作相关的所有类
			3. Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式对象访问方法。Context模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对Context的透明创建的支持。Context模块同时也支持J2EE的一些特性,例如EJB、JMX和基础的远程处理。ApplicationContext接口是Context模块的关键。
			4. Expression Language模块提供了强大的表达式语言,用于在运行时查询和操作对象。它是JSP2.1规范中定义的unifed expression language的扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文(accession the context of arrays)、容器和索引器、逻辑和算数运算符、命名变量以及从Spring的IoC容器中根据名称检索对象。它也支持list投影、选择和一般的list聚合。
			5. **就看懂了1、2点,3、4点目前还没接触到,所以没什么理解。**
	2. Data Access/Integration
		1. Data Access/Integration层包含JDBC、ORM、OXM、JMS和Transaction模块
			1. JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。这个模块包含了Spring对JDBC数据访问进行封装的所有类。
			2. ORM模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射,如前边提到的简单声明性事务管理。
			3. **Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构。**
			4. OXM模块提供了一个对Object/XML映射实现的抽象层,Object/XML映射实现包括JAXB、Castor、XMLBeans、JiBX和XStream。
			5. JMS(Java Messagin Service)模块主要包含了一些制造和消费消息的特性。
			6. Transaction模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的POJO都适用。
			7. 对4、5点不理解。
	3. Web
		1. Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以,Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理大部分请求以及将请求参数绑定到域对象的工作。Web层包含了Web、Web-Servlet、Web-Struts和Web-Porlet模块,具体说明如下。
			1. Web模块:提供了基础的面向Web的集成特性。例如,多文件上传、使用servlet listeners初始化IoC容器以及一个面向Web的应用上下文。它还包含Spring远程支持中Web的相关部分。
			2. Web-Servlet模块web.servlet.jar:该模块包含Spring的model-view-controller(MVC)实现。Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚地分离开来,并与Spring框架地其它特性集成在一起。
			3. Web-Struts模块:该模块提供了对Struts的支持,使得类在Spring应用中能够与一个典型的Struts Web层集成在一起。**注意,该支持在Spring3.0中已被弃用**。
			4. Web-Porlet模块:提供了用于Portlet环境和Web-Servlet模块的MVC的实现。
			5. 感觉这些都很陌生。
	4. AOP
		1. AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性。利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中,这点有点像.Net技术中的attribute概念。
		2. 通过配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中,所以可以很容易地使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB组件,就可以将声明式事务管理集成到应用程序中。
			1. Aspects模块提供了对AspectJ的集成支持。
			2. Instrumentation模块提供了class instrumentation支持和classloader实现,使得可以在特定的应用服务器上使用。
	5. Test
		1. Test模块支持使用JUnit和TestNG对Spring组件进行测试。
           

1.2 环境搭建

1. 由于选取的idea版本及spring的版本不同,会出现不同的错误,不在此处浪费时间。
           

1.3 cglib和objenesis的编译错误解决

1. 我未遇到此问题。
           

第二章 容器的基本实现

1. 源码分析是一件非常煎熬且极具挑战性的任务,你准备好开始战斗了吗?
           

2.1 容器的基本用法

1. bean是Spring中最核心的东西,因为Spring就像是个大水桶,而bean就像是容器中的水,水桶脱离了水便也没什么用处了,那么我们先来看看bean的定义。 
2. 基本用法就是将bean声明式注册到xml文件中,并通过Factory取出bean。不过多赘述。
           

2.2 功能分析

1. 现在我们可以来好好分析一下上面测试代码的功能,来探索上面的代码中Spring究竟帮我们完成了什么工作?不管你之前是否使用过Spring,当然,你都开始关心底层了,你应当使用过。上面那段代码完成的功能无非就是以下几点:
	1. 读取配置文件beanFactoryTest.xml
	2. 根据beanFactoryTest.xml中的配置找到对应类的配置,并实例化。
	3. 调用实例化后的实例。
2. 为了更清楚地描述,如果想要完成我们预想地功能,至少需要3个类:
	1. ConfigReader:用于读取及验证配置文件。我们要用配置文件里面的东西,虽然首先要做的就是读取,然后放置在内存中。
	2. ReflectionUtil:用于根据配置文件中地配置进行反射实例化。比如在上例中beanFactoryTest.xml出现地<bean id="myTestBean" class="bean.MyTestBean"/>,我们就可以根据bean.MyTestBean进行实例化。
	3. App:用于完成整个逻辑地串联。
3. 按照原始地思维方式,整个过程无非如此,但是不幸地是,因为各代大佬地优化,这个过程变得非常复杂。
           

2.3 工程搭建

1. 在Spring源码中,用于实现上面功能地是**org.Springframework.beans.jar**
           

2.4 Spring地结构组成

1. 先尝试梳理Spring的框架结构,从全局的角度了解到Spring的结构组成。
           

2.4.1 beans包的层级结构

1. 先看看整个beans工程的源码结构。
	1. src/main/java用于展现Spring的主要配置
	2. src/main/resources用于存放系统的配置文件
	3. src/test/java 用于对主要逻辑进行单元测试
	4. src/test/resources用于存放测试用的配置文件
           

2.4.2 核心类介绍

1. 通过beans工程的结构介绍,我们现在对beans的工程结构有了初步的认识,但是在正式开始源码分析之前,有必要了解Spring中核心的两个类。
	1. DefaultListableBeanFactory
		1. XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。
		2. 简单了解类图中各个类的作用
			1. AliasRegistry:定义对alias(别名)的简单增删改查。
			2. SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
			3. SingletonBeanRegistry:定义对单例的注册及获取。
			4. BeanFactory:定义获取bean及bean的各种属性。
			5. DefaultSingletonBeanRegistry:对接口SingetonBeanRegistry各函数的实现。
			6. HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。
			7. BeanDefinitionRegistry:定义对BeanDefintion的各种增删改查操作。
			8. FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。
			9. ConfigurableBeanFactory:提供配置Factory的各种方法。
			10. ListableBeanFactory:根据各种条件获取bean的配置清单。
			11. AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
			12. AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器。
			13. AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口Autowire CapableBeanFactory进行实现。
			14. ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。
			15. DefaultListableBeanFactory:综合上面所有功能,主要对bean注册后的处理。
			16. 上面这个只是冰山一角。**快跑**
		3. XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。
2. XmlBeanDefinitionReader
		1. XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先我们看看各个类的功能。
			1. ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。
			2. BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
			3. EnvironmentCapable:定义获取Environment方法。
			4. DocumentLoader:定义从资源文件加载到转换为Document的功能。
			5. AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
			6. BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能。
			7. BeanDefinitionParserDelegate:定义解析Element的各种方法。
			8. 经过上面的分析,我们可以梳理出整个XML配置文件读取的大致流程。
				1. 通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件。
				2. 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件。
				3. 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDenifitionParserDelegte对Element进行解析。
           

2.5 容器的基础XmlBeanFactory

1. 我们接下来深入分析一下功能的代码实现
	1. BeanFactory bf = new XmlBeanFactory(new ClassPathResource(new ClassPathResource("beanFactoryTest.xml")))
2. 上述代码的执行顺序为:在测试的BeanFactoryTest中首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化。那么Resource资源是如何封装的呢?
           

2.5.1 配置文件封装

1. Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource("beanFactoryTest.xml"),那么ClassPathResource完成了什么功能呢?
2. 在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如"file:" "http:" "jar:"等,然而URL没有默认定义相对Classpath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如"classpath:",然而这需要了解URL的实现机制,而且URL也没有提供基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源。
```
	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();
	
	}

```
3. InputStreamSource封装任何能返回InputStream的类,比如File、URL、Classpath下的资源和ByteArray等。它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。
4. Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelatice()。在错误处理中需要详细的打印出错的资源文件,因而Resource还提供了getDescription()方法用来在错误处理中打印信息。
5. 对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。
6. 当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
7. 了解了Spring中将配置文件封装为Resource类型的实例方法后,我们就可以继续探寻XmlBeanFactory的初始化过程了,XmlBeanFactory的初始化有若干办法,Spring中提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的办法,代码如下:
```
	XmlBeanFactory.java
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
```
8. 构造函数内部再次调用内部构造函数
```
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
```
9. 上面函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载真正的实现,也是我们分析的重点之一。XmlBeanDefinitionReader加载数据就是在这里完成的,但是在XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:
	1. super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中:
	```
		public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}
	```
10. 这里有必要提及ignoreDependencyInterface方法。ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
	1. 当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动化初始化B,这也是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的:**自动装配时忽略给定的依赖接口,**典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。
           

2.5.2 加载Bean

1. 之前提到的在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法"this.reader.loadBeanDefinitions(resource)",而这句代码则是整个资源加载的切入点。
2. 整个处理过程如下:
	1. 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装。
	2. 获取输入流。从Resource中获取对应的InputStream并构造InputSource。
	3. 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
3. 我们先来看下loadBeanDefinitions()函数的具体实现过程。
```
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}
```
4. 那么EncodedResource的作用是什么呢?通过名称,我们可以大致推断这个类主要是用于对资源文件的编码进行处理。其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
```
	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());
	}
}
```
5. 上面构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource))。
6. 这个方法内部才是真正的数据准备阶段:
```
	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!");
		}

		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			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();
			}
		}
	}
```
7. 我们再次整理数据准备阶段的逻辑,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。
```
	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);
		}
	}
```
8. 上面冗长的代码中假如不考虑异常类的代码,其实只做了三件事,这三件事的每一件都必不可少:
	1. 获取对XML文件的验证模式。
	2. 加载XML文件,并得到对应的Document。
	3. 根据返回的Document注册Bean信息。
9. 这3个步骤支撑着整个Spring容器部分的实现,尤其是第3步对配置文件的解析,逻辑非常的复杂,我们先从获取XML文件的验证模式讲起。
           

2.6 获取XML的验证模式

1. 了解XML文件都应该知道XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。它们之间有什么区别呢?
           

2.6.1 DTD与XSD区别

1. 略,有兴趣的可以自己去w3c看。
           

2.6.2 验证模式的读取

1. 了解了DTD与XSD的区别后我们再去分析Spring中对于验证模式的提取就更容易理解了。通过之前的分析我们锁定了Spring通过getValidationModeForResource方法来获取对应资源的验证模式。
```
	protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		// Hmm, we didn't get a clear indication... Let's assume XSD,
		// since apparently no DTD declaration has been found up until
		// detection stopped (before finding the document's root tag).
		return VALIDATION_XSD;
	}
```
2. 方法的实现其实还是很简单的,无非是如果设定了验证模式则使用设定的验证模式(可以通过对调用XmlBeanDefinitionReader中的setValidationMode方法进行设定),否则使用自动检测的方式。而自动检测验证模式的功能是在函数detectValidationMode()方法中实现的,在detectValidationMode()函数中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector,调用了XmlValidationModeDetector的validationModeDetector方法,集体代码如下:
           
XmlBeanDefinitionReader.java
```
	protected int detectValidationMode(Resource resource) {
		if (resource.isOpen()) {
			throw new BeanDefinitionStoreException(
					"Passed-in Resource [" + resource + "] contains an open stream: " +
					"cannot determine validation mode automatically. Either pass in a Resource " +
					"that is able to create fresh streams, or explicitly specify the validationMode " +
					"on your XmlBeanDefinitionReader instance.");
		}

		InputStream inputStream;
		try {
			inputStream = resource.getInputStream();
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
					"Did you attempt to load directly from a SAX InputSource without specifying the " +
					"validationMode on your XmlBeanDefinitionReader instance?", ex);
		}

		try {
			return this.validationModeDetector.detectValidationMode(inputStream);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
					resource + "]: an error occurred whilst reading from the InputStream.", ex);
		}
	}
```