前言
不使用框架,我们是如何访问资源的?带着这个思考阅读本文
何为资源
class文件;vm/html文件;图像/声音;xml/xsd等配置文件都可以称之为资源,而我们平常最长做的事情就是定义资源,保存资源,定位并加载资源,而本文就是对此做了阐述。
如何访问资源
由于资源具有类型多样性和存储位置的差异,导致访问资源的方式也千差万别,以下是几个主要的资源访问方法,也是其他框架加载资源的底层方法。
访问ClassPath中的资源
URL resourceURL = getClassLoader().getResource("java/lang/String.class");
InputStream resourceContent = getClassLoader().getResourceAsStream("java/lang/String.class");
访问文件系统中的资源
File resourceFile = new File("c:\\test.txt");
InputStream resourceContent = new FileInputStream(resourceFile);
访问web应用中的资源
URL resourceURL = servletContext.getResource("/WEB-INF/web.xml");
InputStream resourceContent = servletContext.getResourceAsStream("/WEB-INF/web.xml");
访问jar包中的资源
URL jarURL = new File(System.getProperty("java.home") + "/lib/rt.jar").toURI().toURL();
URL resourceURL = new URL("jar:" + jarURL + "!/java/lang/String.class");
InputStream resourceContent = resourceURL.openStream();
上述访问资源的方法都是通过sun的API来实现的资源访问,在 Sun 所提供的标准 API 里,资源访问通常由 java.net.URL 和文件 IO 来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL 类。URL 类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext 的路径来访问资源,虽然 Java 允许使用特定的 URL 前缀注册新的处理类(例如已有的 http: 前缀的处理类),但是这样做通常比较复杂,而且 URL 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。
ClassLoader的资源加载方式
本节主要讨论资源的加载,不涉及双亲委派这些内容,主要涉及到的类有URL,URLClassLoader,URLClassPath,主要解决了以下问题:
1、有哪些资源加载方式?通俗点就是说支持哪些协议?
2、对于网络上的jar资源,同时又依赖了网络端本地的文件资源,如何加载?通俗点就是说协议可以嵌套么?
3、ClassPath如何管理Urls,并且快速定位到指定资源所在的绝对路径的?通俗点就是说难道保存了所有资源和绝对路径的映射么?
4、对于同一个名称的资源,批量加载和单个加载有什么区别?通俗点比喻就是说classpath:和classpath*的区别(当然是不支持这两种协议的啊)?
加载单个文件
先举个加载class文件的例子:大致过程是先预处理所需加载的类名,转换为具体的相对路径的名称,com.alibaba.test->com/alibaba/test,然后依据classPath和该相对路径找到相应的资源,最后defineClass生成应用中需要用到的class实例。
【URLClassPath】
public URL findResource(String name, boolean check) {
Loader loader;
for (int i = 0; (loader = getLoader(i)) != null; i++) {
URL url = loader.findResource(name, check);
if (url != null) {
return url;
}
}
return null;
}
参数name就是转换为相对路径的class文件的位置,每一个classPath管理的url对应于一个loader,因为每个url的加载方式都不同,可以是jar或者file或者net。上面的代码可以看到,遍历所有的loader,找到可以加载该资源的loader,当然了,最差的情况就是遍历完所有的loader才最终找到资源,不过好在classloader缓存了每个查找到的class文件,但是这种代码的设计略欠优雅。盒子里有很多巧克力,现在想找到红色巧克力所在的盒子,元芳,你怎么看?
【URLClassPath】
private synchronized Loader getLoader(int index) {
// Expand URL search path until the request can be satisfied
// or the URL stack is empty.
while (loaders.size() < index + 1) {
// Pop the next URL from the URL stack
URL url;
synchronized (urls) {
if (urls.empty()) {
return null;
} else {
url = (URL)urls.pop();
}
}
// Skip this URL if it already has a Loader. (Loader
// may be null in the case where URL has not been opened
// but is referenced by a JAR index.)
if (lmap.containsKey(url)) {
continue;
}
// Otherwise, create a new Loader for the URL.
Loader loader;
try {
loader = getLoader(url);
// If the loader defines a local class path then add the
// URLs to the list of URLs to be opened.
URL[] urls = loader.getClassPath();
if (urls != null) {
push(urls);
}
} catch (IOException e) {
// Silently ignore for now...
continue;
}
// Finally, add the Loader to the search path.
loaders.add(loader);
lmap.put(url, loader);
}
return (Loader)loaders.get(index);
}
这是个比较重要的类,加载资源的时候再初始化loader,由于有些jar包的url依赖了一些jar包,因此代码将未处理的url置于栈中,每次出栈初始化对应的loader,如果是jar包的url还会将其依赖的class-path压栈处理。下面看看如何依据url来决策loader的加载方式的。
【URLClassPath】
private Loader getLoader(final URL url) throws IOException {
try {
return (Loader)java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction() {
public Object run() throws IOException {
String file = url.getFile();
if (file != null && file.endsWith("/")) {
if ("file".equals(url.getProtocol())) {
return new FileLoader(url);
} else {
return new Loader(url);
}
} else {
return new JarLoader(url, jarHandler, lmap);
}
}
});
} catch (java.security.PrivilegedActionException pae) {
throw (IOException)pae.getException();
}
}
从上面可以看到主要有三种加载方式:默认的加载方式、文件加载方式和jar包的加载方式,决策方式是依据url的协议。这里有个注意点:默认的加载方式和jar协议的加载方式都是支持远程加载的,但是file协议是直接读本地文件。
看看下面几个url应该是采用哪种加载策略:http://www.springframework.org,http://localhost/jetty-all-8.1.7.v20120910.jar,file:///Users/apple/software/External-Jars/jetty-all-8.1.7.v20120910.jar,http://www.springframework.org/
接下来分析默认的加载方式和基于jar包的加载方式:
【URLClassPath&Loader】
URL findResource(final String name, boolean check) {
URL url;
try {
url = new URL(base, ParseUtil.encodePath(name, false));
} catch (MalformedURLException e) {
throw new IllegalArgumentException("name");
}
try {
if (check) {
URLClassPath.check(url);
}
/*
* For a HTTP connection we use the HEAD method to
* check if the resource exists.
*/
URLConnection uc = url.openConnection();
if (uc instanceof HttpURLConnection) {
HttpURLConnection hconn = (HttpURLConnection)uc;
hconn.setRequestMethod("HEAD");
if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
return null;
}
} else {
// our best guess for the other cases
InputStream is = url.openStream();
is.close();
}
return url;
} catch (Exception e) {
return null;
}
}
封装了url,然后直接openConnection了有木有,但如果是本地资源可怎么办,url这个类是怎么区分的?这点也纠结了我好久,其实在new url的时候就会依据协议来指定相应的hanlder,http的自然就是httphandler了,这也就是我们在框架中经常看到openConnection这个方法,确没有配置handler的原因。上面还有一个注意点,打开了流,确没有读数据,我想是为了check的原因吧!毕竟throw了就会返回null了。
下面拿http://localhost/jetty-all-8.1.7.v20120910.jar 这个url来分析JarLoader
【URLClassPath&JarLoader】
Resource getResource(final String name, boolean check) {
if (metaIndex != null) {
if (!metaIndex.mayContain(name)) {
return null;
}
}
try {
ensureOpen();
} catch (IOException e) {
throw (InternalError) new InternalError().initCause(e);
}
final JarEntry entry = jar.getJarEntry(name);
if (entry != null)
return checkResource(name, check, entry);
if (index == null)
return null;
HashSet visited = new HashSet();
return getResource(name, check, visited);
}
ensureOpen这个方法会去依据url生成JarFile,这里当然依据url的协议,这里选择了从远程加载,然后再加载jar包里面的资源,如下。
private JarFile getJarFile(URL url) throws IOException {
// 本地文件直接生成JarFile
if (isOptimizable(url)) {
FileURLMapper p = new FileURLMapper (url);
if (!p.exists()) {
throw new FileNotFoundException(p.getPath());
}
return new JarFile (p.getPath());
}
//网络资源通过http协议缓存在本地的jar_tmp中
URLConnection uc = getBaseURL().openConnection();
uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
return ((JarURLConnection)uc).getJarFile();
}
生成JarFile之后,再加载其中的class等资源
【JarLoader】
Resource checkResource(final String name, boolean check,
final JarEntry entry) {
final URL url;
try {
url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
if (check) {
URLClassPath.check(url);
}
} catch (MalformedURLException e) {
return null;
// throw new IllegalArgumentException("name");
} catch (IOException e) {
return null;
} catch (AccessControlException e) {
return null;
}
return new Resource() {
public String getName() { return name; }
public URL getURL() { return url; }
public URL getCodeSourceURL() { return csu; }
public InputStream getInputStream() throws IOException
{ return jar.getInputStream(entry); }
public int getContentLength()
{ return (int)entry.getSize(); }
public Manifest getManifest() throws IOException
{ return jar.getManifest(); };
public Certificate[] getCertificates()
{ return entry.getCertificates(); };
public CodeSigner[] getCodeSigners()
{ return entry.getCodeSigners(); };
};
}
这篇文档描述的jar文件的加载可以参考下:http://fly-hyp.iteye.com/blog/296625
加载多个文件
这个没什么好解释的
【URLClassPath】
public Enumeration findResources(final String name,
final boolean check) {
return new Enumeration() {
private int index = 0;
private URL url = null;
private boolean next() {
if (url != null) {
return true;
} else {
Loader loader;
while ((loader = getLoader(index++)) != null) {
url = loader.findResource(name, check);
if (url != null) {
return true;
}
}
return false;
}
}
public boolean hasMoreElements() {
return next();
}
public Object nextElement() {
if (!next()) {
throw new NoSuchElementException();
}
URL u = url;
url = null;
return u;
}
};
}
Spring的优化点
个人感觉Spring的资源加载最突出的特点是抽象了Resource,用策略模式的方式加载资源。Resource接口就是策略模式的典型应用,Resource 接口就代表资源访问策略,但具体采用哪种策略实现,Resource 接口并不理会。客户端程序只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样应用可以在不同的资源访问策略之间自由切换。
Resource in spring
Spring为Resource提供了如下实现类:
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
1、UrlResource该类仅仅是对url的一个封装,支持url所支持的那些协议装载资源,特点是无上下文,一般是提供底层服务所用。
public class UrlResourceTest
{
public static void main(String[] args) throws Exception
{
// 创建一个 Resource 对象,指定从文件系统里读取资源
UrlResource ur = new UrlResource("file:book.xml");
// 获取该资源的简单信息
System.out.println(ur.getFilename());
System.out.println(ur.getDescription());
// 创建 Dom4j 的解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(ur.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 此处省略了访问、输出 XML 文档内容的代码。
...
}
}
2、ClassPathResource该类主要功能是访问classpath下的资源,底层依旧采用uri来实现,特点是有上下文,并且增加了class协议
public class ClassPathResourceTest
{
public static void main(String[] args) throws Exception
{
// 创建一个 Resource 对象,从类加载路径里读取资源
ClassPathResource cr = new ClassPathResource("book.xml");
// 获取该资源的简单信息
System.out.println(cr.getFilename());
System.out.println(cr.getDescription());
// 创建 Dom4j 的解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(cr.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 此处省略了访问、输出 XML 文档内容的代码。
...
}
}
3、FileSystemResource
该类的主要功能是访问文件系统下的资源,封装了file和path,特点是功能单一,但是使用 FileSystemResource 也可消除底层资源访问的差异,程序通过统一的 Resource API 来进行资源访问。并且执行 Spring 框架的某方法时,该方法接受一个代表资源路径的字符串参数,当 Spring 识别该字符串参数中包含 file: 前缀后,系统将会自动创建 FileSystemResource 对象。
public class FileSystemResourceTest
{
public static void main(String[] args) throws Exception
{
// 默认从文件系统的当前路径加载 book.xml 资源
FileSystemResource fr = new FileSystemResource("book.xml");
// 获取该资源的简单信息
System.out.println(fr.getFilename());
System.out.println(fr.getDescription());
// 创建 Dom4j 的解析器
SAXReader reader = new SAXReader();
Document doc = reader.read(fr.getFile());
// 获取根元素
Element el = doc.getRootElement();
List l = el.elements();
// 此处省略了访问、输出 XML 文档内容的代码。
...
}
}
虽然提供了如此多的实现类,但是spring只需要和Resource接口耦合,提供一个context,spring会智能的识别出哪个Resource,而这个context就是ApplicationContext自身(自身是有默认资源实现的)和路径参数(当然是根据协议了)。当 Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优势。细节可以参照:
【PathMatchingResourcePatternResolver】
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
4、回顾上面的Resource的类图,ContextResource接口的作用是用来干什么的?
ContextResource接口类只有一个接口方法:“String getPathWithinContext()”,该方法获取Resource自身所在的path,可应用在这样一种场景:一份xml中可能会import一些别的Resource,但是import的这些Resource也许会依赖宿主的为主,因此需要获取宿主的url来定位目标Resource的位置。
正如其名,兼有Resource和Context的功能!
ResourceLoader in spring
Spring框架的ApplicationContext 不仅是 Spring 容器,而且它还是资源访问策略的“决策者”,也就是策略模式中 Context 对象,它将为客户端代码“智能”地选择策略实现,当ApplicationContext 实例获取 Resource 实例时,系统将默认采用与 ApplicationContext 相同的资源访问策略。
画图是门艺术活啊!~
ResourceLoader的加载策略如下:
1、解析资源名称,如果前缀带有“classpath*”,转到2,否则转到3;
2、判断是否符合ANT风格的模式,如果匹配,转至4,否则转至5;
3、判断是偶符合ANT风格的模式,如果匹配,转至4,否则转至6;
4、解析匹配目标路径的所有资源,并加载;
5、加载classpath下该名称的资源组;
6、判断资源名称,若带有"classpath"前缀,转至7,否则转至8;
7、加载classpath下该名称的资源;
8、依据资源名称的url判断该资源是否存在,如果存在,转至9,否则转至10;
9、通过SUN API的URL类来加载该资源
10、采用ApplicationContext的默认资源加载策略定义该Resource的身份并加载,比如ClassPathXmlApplicationContext的默认加载资源为:ClassPathContextResource。
常见的加载协议有:
1、classpath:以 ClassPathResource 实例来访问类路径里的资源。
2、file:以 UrlResource 实例访问本地文件系统的资源。
3、http:以 UrlResource 实例访问基于 HTTP 协议的网络资源。
4、无前缀:由于 ApplicationContext 的实现类来决定访问策略。
Spring的Resource注入
Spring 提供的资源访问策略,但这些依赖访问策略要么需要使用 Resource 实现类,要么需要使用 ApplicationContext 来获取资源。实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。Spring 框架不仅充分利用了策略模式来简化资源访问,而且还将策略模式和 IoC 进行充分地结合,最大程度地简化了 Spring 资源访问。因此Bean实例要访问资源,有两种方案:
1、代码中获取 Resource 实例。
2、使用依赖注入。
对于第一种方式的资源访问,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入资源。下面举个依赖注入的例子:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定 Spring 配置文件的 DTD 信息 -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN">
<!-- Spring 配置文件的根元素 -->
<beans>
<bean id="test" class="lee.TestBean">
<!-- 注入资源 -->
<property name="resource"
value="classpath:book.xml"/>
</bean>
</beans>
public class TestBean
{
private Resource res;
// 依赖注入 Resource 资源的 setter 方法
public void setResource(Resource res)
{
this.res = res;
}
至于里面的原理,则是通过Spring的BeanFactoryPostProcessor拓展机制来实现的:
【ResourceEditorRegistrar】
public void registerCustomEditors(PropertyEditorRegistry registry) {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader);
registry.registerCustomEditor(Resource.class, baseEditor);
registry.registerCustomEditor(InputStream.class, new InputStreamEditor(baseEditor));
registry.registerCustomEditor(File.class, new FileEditor(baseEditor));
registry.registerCustomEditor(URL.class, new URLEditor(baseEditor));
ClassLoader classLoader = this.resourceLoader.getClassLoader();
registry.registerCustomEditor(Class.class, new ClassEditor(classLoader));
registry.registerCustomEditor(URI.class, new URIEditor(classLoader));
if (this.resourceLoader instanceof ResourcePatternResolver) {
registry.registerCustomEditor(Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader));
}
}
批量注入,WEBX3文档里面有这样的例子:
public void setLocations(URL[] resources) {
this.resources = resources;
}
<property name="locations" value="WEB-INF/webx-*.xml" />
但是对比上面的代码发现,貌似只支持Resource[]类型的批量注入,URL[]经过测试也是行不通的,此处希望读者能指出我理解上的错误,或者webx3文档这处写得有问题!
Spring资源加载的缺点
1、不透明性
必须使用详细的路径来访问资源。如果需要修改资源文件位置,那就需要修改所有的引用。说到底就是对于资源文件的管理力度(笔者觉得SpringExt做出的改进源自于此)不够。
2、无扩展性
无法增加新的资源加载方式。
ps:webx3文档中还提出了“鱼和熊掌不可兼得”的缺点,其实不尽然,就算是使用了FileSystemXmlApplicationContext,你也可以指明资源的访问协议来替换默认的加载方式。
那么针对于前两种缺点,SpringExt做了哪些改进呢?
SpringExt资源加载的扩展点
替换Spring的ResourceLoader
A、如何配置
当你不使用它时,Spring原有的ResourceLoader功能不受影响;但当你在spring配置文件中添加Resource Loading服务时,ResourceLoader即被切换到新的机制。新的机制可兼容原有的Spring配置和代码,但支持更多的资源装载方式,以及更多的功能,如资源重命名、资源重定向等。你只需要在配置文件中增加以下内容,就可以将Spring ResourceLoader机制替换成Webx的Resource Loading服务:
<resource-loading>
<resource-alias pattern="/" name="/webroot" />
<resource pattern="/webroot" internal="true">
<res-loaders:webapp-loader />
</resource>
<resource pattern="/classpath" internal="true">
<res-loaders:classpath-loader />
</resource>
</ resource-loading>
B、新的ResourceLoader如何替换为Spring原生态的实现,又如何兼容原有的功能?
1、自定义的ResourceLoader与ResourcePatternResolver兼容原有spring的功能
protected Resource getResourceByPath(String path) {
Resource resource = null;
if (resourceLoadingExtender != null) {
resource = resourceLoadingExtender.getResourceByPath(path);
}
if (resource == null) {
resource = super.getResourceByPath(path);
}
return resource;
}
/**
* 扩展<code>ResourcePatternResolver</code>机制,实现自定义的资源装载。
*/
@Override
protected ResourcePatternResolver getResourcePatternResolver() {
final ResourcePatternResolver defaultResolver = super.getResourcePatternResolver();
return new ResourcePatternResolver() {
public Resource[] getResources(String locationPattern) throws IOException {
ResourcePatternResolver resolver = null;
if (resourceLoadingExtender != null) {
resolver = resourceLoadingExtender.getResourcePatternResolver();
}
if (resolver == null) {
resolver = defaultResolver;
}
return resolver.getResources(locationPattern);
}
2、RespurceLoadingService是一个受容器管理的Bean,那么它的生命周期始于何时,初始化又始于何时?
public class ResourceLoadingSupport implements ResourceLoadingExtender, ApplicationListener
【ResourceLoadingSupport】
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
contextRefreshed = true;
resourceLoadingService = getResourceLoadingServiceFromContext();
}
}
【ResourceLoadingSupport】
private ResourceLoadingService getResourceLoadingServiceFromContext() {
try {
return (ResourceLoadingService) factory.getBean(resourceLoadingServiceName);
} catch (IllegalStateException e) {
// beanFactory未准备好,试一下parent factory。如果均取不到ResourceLoadingService,返回null,不打日志
ApplicationContext parent = factory.getParent();
if (parent != null) {
try {
return (ResourceLoadingService) parent.getBean(resourceLoadingServiceName);
} catch (Exception ee) {
}
}
} catch (NoSuchBeanDefinitionException e) {
if (!complained) {
complained = true;
log.warn("ResourceLoadingService does not exists: beanName={}", resourceLoadingServiceName);
}
}
因此可以看到,它的生命周期始于容器启动时,而初始化于容器refresh之后。
SpringExt扩展的功能点
SpringExt把资源加载的方式和资源的类型集中管理了起来,因此体现了易拓展和透明的特点。下面结合代码和功能点来看看有哪些拓展点。
1、重命名资源
优点:环境无关性。
【AbstractResourceLoadingContext】
if (findBestMatch()) {
// findBestMatch() 情况1. 找到alias,但没有找到最终的resource mapping
if (lastMatchedPattern instanceof ResourceAlias) {
if (parent != null) {
log.trace("Resource \"{}\" not found. Trying to find it in super ResourceLoadingService",
resourceName);
try {
resource = loadParentResource(resourceName, options);
} catch (ResourceNotFoundException e) {
// alias将改变resourceName,故保存异常作为caused by异常
chainingException = e;
}
}
} else {
// findBestMatch() 情况2, 3. 找到resource mapping
ResourceLoaderMapping mapping = (ResourceLoaderMapping) lastMatchedPattern;
resource = loadMappedResource(mapping, options);
2、多个Loader查找资源
优点:封装了逻辑,更易于使用。
【AbstractResourceLoadingContext】
// 这里的mapping保存了所有的loading list
resource = loadMappedResource(mapping, options);
3、重定向资源
优点:方便我们拓展资源的定位,比如可以加载特定目录下的cms模板。
这就是一个pattern的优先级的问题,没什么好说的,举个例子:
<resource-loading=>
...
<resource-alias pattern="/templates" name="/webroot/templates" />
<resource pattern="/templates/cms">
<res-loaders:file-loader basedir="${cms_root}" />
</resource>
<resource pattern="/webroot" internal="true">
<res-loaders:webapp-loader />
</resource>
...
</resource-loading>}
4、内容修改,filter的概念
优点:方便我们拓展资源查找的逻辑,比如转换资源文件的格式。
【ResourceLoaderContextImpl】
public Resource getResource() throws ResourceNotFoundException {
if (filtersMatcher.matches(resourceName)) {
ResourceFilterMapping filterMapping = filtersMatcher.bestMatchPettern;
lastMatchedPattern = filterMapping;
lastSubstitution = new MatchResultSubstitution(filtersMatcher.bestMatchResult);
log.debug("Resource \"{}\" matched resource-filters pattern: \"{}\"", resourceName,
filterMapping.getPatternName());
ResourceFilterChain root = this; // as ResourceFilterChain
ResourceMatchResult matchResult = this; // as ResourceMatchResult
ResourceFilterChain chain = filterMapping.getResourceFilterChain(root);
return chain.doFilter(matchResult, options); // with filters
} else {
return doLoad(resourceName, options); // no filters
}
5、定义新的资源,新的pattern格式
优点:方便我们分类管理各个类型的资源,比如:/a,/b可以分开管理加载逻辑。
总结
ClassLoader加载资源具有资源多样化,加载方式多样化的特点;Spring针对这一问题提出了策略模式加载资源的解决方法;但是对于大的工程,资源不进行管理会显得杂乱无章,因此SpringExt主要解决了资源管理的问题。