【Spring系列】- ApplicationContext
文章目录
ApplicationContext构建于BeanFactory之上,除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等
- Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为ApplicationContext类型容器提供了以下几个常用的实现:
- FileSystemXmlApplicationContext:从文件系统加载bean定义及相关资源的ApplicationContext实现
- ClassPathXmlApplicationContext:从Classpath加载bean定义及相关资源的ApplicationContext实现
- XmlWebApplicationContext:Spring提供的用于Web应用程序的ApplicationContext实现
一、统一资源加载策略
URL全名是Uniform Resource Locator(统一资源定位器),但有些名不副实
-
说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能
资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如文件系统中、Java应用的Classpath中,甚至存在于URL可以定位的地方
-
其次,Java SE提供的标准类java.net.URL类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。
所以,针对于此,Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和统一资源加载策略
二、Spring中的Resource
Spring框架内部使用Resource接口作为抽象了所有资源的 资源访问接口。该接口定义了7个方法,可以帮助我们查询资源状态、访问资源内容,甚至根据当前资源创建新的相对资源
可以根据资源的不同类型或资源所处的不同场合,给出相应的对Resource接口的具体实现。Spring框架提供了一些实现类:
-
ByteArrayResource: 将字节数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArrayInputStream并返回
-
ClassPathResource: 该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器进行资源加载,如:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
-
FileSystemResource: 对java.io.File类型的封装,可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以
-
UrlResource: 通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作
-
InputStreamResource: 将给定的InputStream视为一种资源的Resource实现类,较为少用。可能的情况下,以ByteArrayResource以及其他形式资源实现代之
如果以上这些资源实现还不能满足要求,那么还可以根据相应场景自己实现Resource接口的实现类
三、ResourceLoader,“更广义的URL”
资源有了后,通过ResourceLoader来查找和定位这些资源,org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出
ResourceLoader实现类中,最主要的就是Resource getResource(String location);
方法,通过它可以根据指定的资源位置,定位到具体的资源实例
1. 可用的 ResourceLoader
-
DefaultResourceLoader
作为ResourceLoader默认的实现类,该类默认的资源查找处理逻辑如下:
- 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回
- 否则,(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;(b)如果还是无法根据资源路径定位指定的资源,则委派DefaultResourceLoader的getResourceByPath(String)方法来定位,构造一个实际上并不存在的ClassPathResource类型的资源并返回
-
FileSystemResourceLoader
FileSystemResourceLoader继承自DefaultResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并返回FileSystemResource类型的资源
避免了DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,使我们可以取得预想的资源类型
2. ResourcePatternResolver,批量查找的ResourceLoader
- ResourcePatternResolver接口是ResourceLoader接口的扩展(继承了),ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例
- ResourcePatternResolver引入了Resource[] getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。它同时还引入了一种新的协议前缀classpath*:,针对这一点的支持,将由相应的子类实现给出
- ResourcePatternResolver最常用的一个实现是PathMatchingResourcePatternResolver,该实现类支持ResourceLoader级别的资源加载,支持基于Ant风格的路径匹配模式(类似于**/.suffix之类的路径形式),支持ResourcePatternResolver新增加的classpath:前缀等
- 在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader,如果不指定的话,则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径,委派给它的ResourceLoader来查找和定位资源。这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同,只存在返回的Resource数量上的差异
3. Spring的统一资源加载策略
三、ApplicationContext与 ResourceLoader
-
ApplicationContext支持Spring内统一资源加载策略的的原因:
ApplicationContext继承了ResourcePatternResolver,当然就间接实现了ResourceLoader接口。故任何的ApplicationContext实现类都可以看作是一个ResourceLoader甚至ResourcePatternResolver
-
通常,所有的ApplicationContext实现类会直接或者间接地继承AbstractApplicationContext
-
AbstractApplicationContext又继承了DefaultResourceLoader,它的getResource(String)自然用的DefaultResourceLoader;AbstractApplicationContext类的内部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为PathMatchingResourcePatternResolver,PathMatchingResourcePatternResolver构造时,直接填的AbstractApplicationContext自身作为ResourceLoader
说白了,ApplicationContext的实现类在作为ResourceLoader或者ResourcePatternResolver时的行为,完全就是委派给了DefaultResourceLoader和PathMatchingResourcePatternResolver来做
故ApplicationContext可以有如下新功能:
1. 用于扮演 ResourceLoader
- 既然ApplicationContext可以作为ResourceLoader或者ResourcePatternResolver来使用,故可以通过ApplicationContext的实现类来加载任何Spring支持的Resource类型
2. 用于 ResourceLoader类型的注入
- 在大部分情况下,如果某个bean需要依赖于ResourceLoader来查找定位资源,我们可以为其注入容器中声明的某个具体的ResourceLoader实现。该bean也无需实现任何接口,直接通过构造方法注入或者setter方法注入规则声明依赖即可
- 不过,ApplicationContext容器本身就是一个ResourceLoader,单独提供一个resourceLoader实例有些多于了。如果不介意bean定义依赖于Spring的API,那可以用Spring提供的几个对ApplicationContext特定的Aware接口,包括ResourceLoaderAware和ApplicationContextAware接口
- Foo类通过实现上述接口之一,即可将当前的ApplicationContext容器作为ResourceLoader注入Foo类
- 现在,容器启动时,ApplicationContext类型容器可以自动识别Aware接口,并将当前ApplicationContext容器本身注入到Foo类中
3. 用于 Resource类型的注入
- 容器可以将bean定义文件中的字符串形式表达的信息,正确地转换成具体对象定义的依赖类型。对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用
- BeanFactory容器不会为Resource类型提供相应的PropertyEditor,所以,如果想注入Resource类型的bean定义,就需要注册自定义的PropertyEditor到BeanFactory容器
- 不过,对于ApplicationContext,无需这么做,因为ApplicationContext容器可以正确识别Resource类型并转换后注入相关对象,只需配置Resource依赖到配置文件就行了
- 原因在于: ApplicationContext启动伊始,会通过一个ResourceEditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个PropertyEditor叫做ResourceEditor。这样, ApplicationContext就可以正确地识别Resource类型的依赖了
- ResourceEditor的实现:把配置文件中的路径让ApplicationContext作为ResourceLoader来定位一下
4. 特定情况下,ApplicationContext的 Resource加载行为
-
特定的ApplicationContext容器实现,在作为ResourceLoader加载资源时,会有其特定的行为
-
对于URL所接受的资源路径来说,通常开始都会有一个协议前缀,比如file:、http:、ftp:等。既然Spring使用UrlResource对URL定位查找的资源进行了抽象,那么,同样也支持这样类型的资源路径,在这个基础上,Spring还扩展了协议前缀的集合
- ResourceLoader中增加了一种新的资源路径协议
classpath:
,ResourcePatternResolver增加了classpath*:
- 这样就可以通过这些资源路径协议前缀,明确告知Spring容器要从classpath中加载资源,代码中配置中都能用
- ResourceLoader中增加了一种新的资源路径协议
-
ApplicationContext容器的两种实现类ClassPathXmlApplicationContext和FileSystemXmlApplicationContext在处理资源加载时的默认行为有所不同
- 当ClassPathXmlApplicationContext在实例化的时候,即使没有指明
classpath:
或classpath*:
等前缀,它会默认从classpath中加载bean定义配置文件,即以下两种实例化方式效果是相同的:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml"); ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:conf/appContext.xml");
- 而FileSystemXmlApplicationContext则有些不同,如果代码中只有
"conf/appContext.xml"
,而没有classpath:
,它会尝试从文件系统中加载bean定义文件;增加classpath:
前缀后才从classpath中加载bean定义的配置文件 - 这时它对应的才是ClassPathResource类型的资源,而不是默认的FileSystemResource类型资源。FileSystemXmlApplicationContext之所以如此,是因为它与FileSystemResourceLoader一样,也覆写了DefaultResourceLoader的getResourceByPath(String)方法
- 当ClassPathXmlApplicationContext在实例化的时候,即使没有指明
-
当容器实例化并启动完毕,要用相应容器作为ResourceLoader来加载其他资源时,各种ApplicationContext容器的实现类依然会有不同的表现
- 对于ClassPathXmlApplicationContext,如果不指定路径之前的前缀,它会从Classpath中加载资源
- 对于FileSystemXmlApplicationContext,它将从文件系统中加载该文件。但是,也可以在这个时候用
classpath:
前缀强制指定从Classpath中加载该文件
<bean id="..." class="..."> <property name="..." value="classpath:conf/appContext.xml"/> </bean>
四、国际化信息支持 I18n
要让我们的应用程序可以供全世界不同国家和地区的人们使用,应用程序就必须支持它所面向的国家和地区的语言文字,为不同的国家和地区的用户提供他们各自的语言文字信息
程序的国际化涉及许多的内容,如货币形式的格式化、时间的表现形式、各国家和地区的语言文字等
对于Java中的国际化信息处理,主要涉及两个类,即java.util.Locale和java.util.ResourceBundle