Spring5:就这一次,搞定资源加载器之DefaultResourceLoader

Spring提供了一套资源加载接口及实现类,结构如下:


顶级接口ResourceLoader仅提供了一个getResource(String location)方法,可以根据一个资源地址加载资源文件,资源地址的表达式可以是以下几种:

1. classpath:前缀开头的表达式,例如: classpath:smart-context.xml

2.“/”开头的表达式,例如:/WEB-INF/classes/smart-context.xml

3. 非“/”开头的表达,例如:WEB-INF/classes/smart-context.xml

4. url协议,例如:file:/D:/ALANWANG-AIA/Horse-workspace/chapter3/target/classes/smart-context.xml

注意,该方法不支持解析ant风格的资源路径表达式。

Spring提供了实现类DefaultResourceLoader,DefaultResourceLoader在实现了以上列举的功能基础上,还为开发者提供了自定义扩展接口ProtocolResolver,开发者可实现该接口定制个性化资源表达式,代码如下:

	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		for (ProtocolResolver protocolResolver : this.protocolResolvers) {       // 1
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {return resource;}
		}

		if (location.startsWith("/")) {return getResourceByPath(location);}       //2
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {                     //3
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);                             //4
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);                      //5
			}
		}
	}

步骤1,先用扩展协议解析器解析资源地址并返回。举个例子,咱们可以自定义资源解析器来完成带前缀“classpath:”的解析:

首先实现ProtocolResolver接口:

class ClasspathPreProtocolResolver implements ProtocolResolver{
		   private static String CLASS_PATH_PRE="classpath:";		
		public Resource resolve(String location, ResourceLoader resourceLoader) {
		   if( location.startsWith(CLASS_PATH_PRE)) {
				return new ClassPathResource(location.substring(CLASS_PATH_PRE.length()));
		   }	   
		   return null;
		}		
	}

接着测试:

public class Test {

	public static void main(String[] args) {	
	        DefaultResourceLoader bf = new DefaultResourceLoader();
		bf.getProtocolResolvers().add(new ClasspathPreProtocolResolver());		
		try {
			System.out.println(bf.getResource("smart-context.xml").getURI().toURL());
		} catch (Exception e) {
			e.printStackTrace();
		} 		
	}
}

执行结果:

file:/D:/ALANWANG-AIA/Horse-workspace/chapter3/target/classes/smart-context.xml

步骤2,假设location以斜杠开头,则调用该类中 getResourceByPath(String path)方法 ,代码如下:

	protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}

getResourceByPath(String path)方法 返回ClassPathContextResource对象,ClassPathContextResource是该类中的一个内部类:

/**
	 * ClassPathResource that explicitly expresses a context-relative path
	 * through implementing the ContextResource interface.
	 */
	protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {

		public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
			super(path, classLoader);
		}

		@Override
		public String getPathWithinContext() {
			return getPath();
		}

		@Override
		public Resource createRelative(String relativePath) {
			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
			return new ClassPathContextResource(pathToUse, getClassLoader());
		}
	}

ClassPathContextResource继承自ClassPathResource并实现了ContextResource,因此该类可以表示资源的地址为web容器中的项目的类文件存放路径,假设咱们现有一个web项目叫chapter3,则ClassPathContextResource可以表示/WEB-INF/classes/下的资源文件。咱们可以在web.xml中做如下配置:

<context-param>
    <param-name>contextConfigLocation</param-name>
	<param-value>/WEB-INF/classes/smart-context.xml</param-value>
</context-param>

启动web容器观察输出,成功。

[INFO] Initializing Spring root WebApplicationContext
16:01:24.877 [PathWatcher@679d0be8] INFO  org.springframework.beans.factory.xml.
XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resou
rce [/WEB-INF/classes/smart-context.xml]

步骤三,假如资源表达式以classpath开头,则截取除前缀calsspath:的路径,并做为ClassPathResource的构造参数,生成ClassPathResource实例后返回。咱们可以在web.xml中做如下配置:

<context-param>
    <param-name>contextConfigLocation</param-name>
	<param-value>classpath:smart-context.xml</param-value>
</context-param>

启动服务器,观察输出,成功。

[INFO] Initializing Spring root WebApplicationContext
16:08:01.603 [PathWatcher@679d0be8] INFO  org.springframework.beans.factory.xml.
XmlBeanDefinitionReader - Loading XML bean definitions from class path resource
[smart-context.xml]

步骤四,如果以上的三个步骤都加载失败,则尝试使用url的方式来加载,因此咱们也可以在web.xml做如下配置:

<context-param>
    <param-name>contextConfigLocation</param-name>
	<param-value>file:/D:/ALANWANG-AIA/Horse-workspace/chapter3/target/classes/smart-context.xml</param-value>
</context-param>

启动服务器,观察输出,成功。

[INFO] Initializing Spring root WebApplicationContext
16:11:31.354 [PathWatcher@679d0be8] INFO  org.springframework.beans.factory.xml.
XmlBeanDefinitionReader - Loading XML bean definitions from URL [file:/D:/ALANWA
NG-AIA/Horse-workspace/chapter3/target/classes/smart-context.xml]

步骤五,如果url的方式也加载失败了,则尝试再次使用classpath 的方式加载,这时,咱们的web.xml 可以这样写:

<context-param>
    <param-name>contextConfigLocation</param-name>
	<param-value>WEB-INF/classes/smart-context.xml</param-value>
</context-param>

启动服务器,观察输出,成功。

[INFO] Initializing Spring root WebApplicationContext
16:16:05.256 [PathWatcher@679d0be8] INFO  org.springframework.beans.factory.xml.
XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resou
rce [/WEB-INF/classes/smart-context.xml]

至此,DefaultResourceLoader的代码就分析完了,从分析的过程中咱们可以得出结论:

1.spring对框架的使用者非常友好,很多地方都留有接口供使用者扩展,而且是优先调用。

2.DefaultResourceLoader虽然提供了众多的资源表达式,但是带前缀classpath:在书写上比其他几种更为简便,在执行顺序上位于第二,仅次于扩展接口,那么在效率上也优于其他书写方式(虽然可以忽略不计...)

3.带classpath前缀的写法更易于移植,相对于一般的java程序,classpath指向的是类文件存放的位置,而对于web应用来说,classpath指向的是WEB-INF/classes,因此classpath: 的写法还有屏蔽不同应用类型的作用,实际开发中也以classpath:前缀的写法居多,所以在日后的开发中,我选classpath:前缀。

4.DefaultResourceLoader的getResource(String location)可以看作为是一个模板方法,getResourceByPath(String path) 是钩子函数,只不过DefaultResourceLoader自身也为其提供了实现,

protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}

因此DefaultResourceLoader可以单独使用,也可以搭配子类。子类通过重写getResourceByPath(String path)来返回和自身相关的资源类型,从而达到利用不同方式加载资源的目的。

以上是个人拙见,有错误的地方还望指正,感激不尽,谢谢。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值