ResourceLoader

前言

不使用框架,我们是如何访问资源的?带着这个思考阅读本文

何为资源

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.orghttp://localhost/jetty-all-8.1.7.v20120910.jarfile:///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提供了如下实现类:

ResourceLoader_第1张图片

这些 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_第2张图片

画图是门艺术活啊!~

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原生态的实现,又如何兼容原有的功能?

ResourceLoader_第3张图片

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主要解决了资源管理的问题。


你可能感兴趣的:(spring,url,resource,ResourceLoader)