前言

作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessorBeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。真是“青出于蓝而胜于蓝”啊!

Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为ApplicationContext类型容器提供了以下几个常用的实现。

  • org.springframework.context.support.FileSystemXmlApplicationContext。在默认情况下,从文件系统加载bean定义以及相关资源的ApplicationContext实现。

  • org.springframework.context.support.ClassPathXmlApplicationContext。在默认情况下,从Classpath加载bean定义以及相关资源的ApplicationContext实现。

  • org.springframework.web.context.support.XmlWebApplicationContext。Spring提供的用于Web应用程序的ApplicationContext实现,我们将在第六部分更多地接触到它。

更多实现可以参照org.springframework.context.ApplicationContext接口定义的Javadoc,这里不再赘述。

第4章中说明了ApplicationContext所支持的大部分功能。下面主要围绕ApplicationContext较之BeanFactory特有的一些特性展开讨论, 即国际化(I18n)信息支持、统一资源加载策略以及容器内事件发布等。

统一资源加载策略

要搞清楚Spring为什么提供这么一个功能,还是从JavaSE提供的标准类java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。

首先,说是统一资源定位,但基本实现却 只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

其次,从某些程度上来说, 该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。

所以,在这个前提下,Spring提出了一套基于org.springframework.core.io.Resourceorg.springframework.core.io.ResourceLoader接口的资源抽象和加载策略

Spring中的Resource

Spring框架内部使用org.springframework.core.io.Resource接口作为 所有资源的抽象和访问接口,我们之前在构造BeanFactory的时候已经接触过它,如下代码:

    BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
 

其中ClassPathResource就是Resource的一个特定类型的实现,代表的是位于Classpath中的资源。

Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类(可以在org.springframework.core.io包下找到这6些实现类)。

  • ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArrayInputStream并返回。

  • ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。

  • FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。

  • UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作。

  • InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。

如果以上这些资源实现还不能满足要求,那么我们还可以根据相应场景给出自己的实现,只需实现org.springframework.core.io.Resource接口就是了。不过,要真想实现自定义的Resource,倒是真没必要直接实现该接口,我们可以继承org.springframework.core.io.AbstractResource抽象类,然后根据当前具体资源特征,覆盖相应的方法就可以了。

ResourceLoader ,“更广义的URL”

资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。**org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。**我想,把ResourceLoader称作统一资源定位器或许才更恰当一些吧!ResourceLoader定义如下:

 
    public interface ResourceLoader {
    	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
      Resource getResource(String location);
    	ClassLoader getClassLoader();
    }

其中最主要的就是 Resource getResource(String location); 方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。

1. 可用的ResourceLoader

DefaultResourceLoader

ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResourceLoader,该类默认的资源查找处理逻辑如下。

(1)首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回。

(2)否则,(a)尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;(b)如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String)方法来定位,DefaultResourceLoadergetResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。

如果最终没有找到符合条件的相应资源,getResourceByPath(String)方法就会构造一个实际上并不存在的资源并返回。

FileSystemResourceLoader

为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自DefaultResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以FileSystemResource类型返回。这样,我们就可以取得预想的资源类型。

(原文这里给了DefaultResourceLoader和FileSystemResourceLoader的代码例子)

2. ResourcePatternResolver ——批量查找的ResourceLoader

ResourcePatternResolverResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。

ResourcePatternResolver最常用的一个实现是org.springframework.core.io.support.PathMatchingResourcePatternResolver

在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader,如果不指定的话,则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径,委派给它的ResourceLoader来查找和定位资源。这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同,只存在返回的Resource数量上的差异。

(这里不做具体介绍,想了解的话可以看原文)

3. 回顾与展望

现在我们应该对Spring的统一资源加载策略有了一个整体上的认识,就如图5-1所示。

image-20220405130156615

虽然现在看来比较“单薄”,不过,稍后,我们就会发现情况并非如此了。