不使用框架,我们是如何访问资源的?带着这个思考阅读本文
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 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。
本节主要讨论资源的加载,不涉及双亲委派这些内容,主要涉及到的类有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的资源加载最突出的特点是抽象了Resource,用策略模式的方式加载资源。Resource接口就是策略模式的典型应用,Resource 接口就代表资源访问策略,但具体采用哪种策略实现,Resource 接口并不理会。客户端程序只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样应用可以在不同的资源访问策略之间自由切换。
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的功能!
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 实现类,要么需要使用 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文档这处写得有问题!
1、不透明性
必须使用详细的路径来访问资源。如果需要修改资源文件位置,那就需要修改所有的引用。说到底就是对于资源文件的管理力度(笔者觉得SpringExt做出的改进源自于此)不够。
2、无扩展性
无法增加新的资源加载方式。
ps:webx3文档中还提出了“鱼和熊掌不可兼得”的缺点,其实不尽然,就算是使用了FileSystemXmlApplicationContext,你也可以指明资源的访问协议来替换默认的加载方式。
那么针对于前两种缺点,SpringExt做了哪些改进呢?
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把资源加载的方式和资源的类型集中管理了起来,因此体现了易拓展和透明的特点。下面结合代码和功能点来看看有哪些拓展点。
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主要解决了资源管理的问题。