Resource——资源
对于一个联机事务型系统(业务系统)来说,所依赖的外部运行信息主要有2个来源:数据项和资源项。数据项的存放位置通常是使用各种关系性或NoSql数据库,而资源项通常是使用文件、网络信息的方式来存储。
早在JDK1.0的时代Java就已经提供了本地资源和网络资源的读取功能——java.net.URL。他可以同时管理本地资源(操作系统资源)以及网络资源,如下面这个例子:
public class ResourceApp {
public static void main(String[] args) throws MalformedURLException {
//读取本地资源
URL url = ResourceApp.class.getResource("/extend.properties");
print(url);
//读取互联网资源
url = new URL("http", "www.baidu.com", 80, "");
print(url);
url = new URL("https", "www.chkui.com", 443, "/174870bb04.js");
print(url);
}
}
// 输出
// file:/work/chkui/spring-core-sample/bin/main/extend.properties
// http://www.baidu.com:80
// https://www.chkui.com:443/174870bb04.js对于每一个类来说getResource方法可以获取当前类所在的系统路径(getResource("")),以及classpath的路径(getResource("/")),利用这个功能我们可以获取操作系统上所知的任何资源。除了本地文件,URL也可以通过域名规则来获取网络上的资源。
注意输出内容中的开头
URL指向某一个资源之后,可以使用URL::openStream或URL::getFile等方法进一步获取文件中的内容:
public class ResourceApp {
public static void main(String[] args) throws MalformedURLException {
Url url = new URL("https", "www.chkui.com", 443, "/174870bb04.js");
try(InputStream is = url.openStream()){
byte[] buffer = new byte[1024*1024];
is.read(buffer);
String content = new String(buffer, Charset.forName("UTF-8"));
print("Content :", content);
} catch (IOException e) {
}
}
}Spring中的资源管理
Spring的资源管理在JDK的基础功能上进行了强大的扩展,即使你不用Spring的整个生态或者容器,你也可以将其资源管理作为一个工具整合到自己的系统中而提高效率。它扩展了以下内容:
- 隐藏底层实现。对于各种各样的资源Spring都使用了不同的实现类来管理,但是他利用适配器模式让使用者仅仅需要了解org.springframework.core.io.Resource接口即可。
- 新增资源存在判断、资源操作权限相关的功能,相对于java.net.URL资源不存在则设置为null更友好。
- 支持通配符来获取资源,例如 :classpath:a/b/**/applicationContext-*.xml。
协议与路径
在前面的内容中就提到了多个协议,spring的资源管理功能除了标准的协议,还增加了一个——
| 协议 | 例子 | 说明 |
|---|---|---|
| classpath: | classpath:res/extend.properties | 从当前jvm的classpath根路径开始获取资源。 |
| file: | file:///tmp/myfile.data | 从操作系统(文件路径)的路径获取资源。 |
| http(s): | http(s)://www.chkui.com/ | 从互联网获取资源。 |
| (无标记) | /data/extend.data | 根据应用上下文获取资源。 |
在上下文与IoC这篇文章中已经介绍过,经过层层继承和实现,Spring提供容器实现功能的主要是
ApplicationContext ctx = new ClassPathXmlApplicationContext("config/ctx.xml");ClassPathXmlApplicationContext默认启用的是
ApplicationContext ctx = new ClassPathXmlApplicationContext("file:///config/ctx.xml");通过协议明确告知路径规则,那么在
而
上面的内容提到了
获取资源的方法
直接使用ApplicationContext
在明确所支持的协议之后,我们就可以用ResourcePatternResolver::getResources方法来获取资源。ApplicationContext继承了ResourcePatternResolver接口,所以我们通常使用以下方法获取资源:
package chkui.springcore.example.resource;
public class ResourceApp {
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(ResourceApp.class);
Resource res = ctx.getResource("classpath:extend.properties");
print("Resource :", res);
res = ctx.getResource("https://www.chkui.com");
print("Resource :", res);
}
}ResourceLoaderAware注入
除了直接使用ApplicationContext,还可以通过继承ResourceLoaderAware的方式来获取资源加载接口:
package chkui.springcore.example.resource;
public class LoadResourceBean implements ResourceLoaderAware{
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
Resource res = resourceLoader.getResource("classpath:extend.properties");
System.out.println("Bean load Resource :" + res);
}
}
实际上这里传入进来的ResourceLoader就是ApplicationContext,所以用ApplicationContextAware也可以实现对应的功能。但是为了明确功能的用途,这里最好还是实现ResourceLoaderAware比较合理。
Autowired注入
在2.5.x之后,spring可以使用@Autowired注解引入ResourceLoader(ApplicationContext):
package chkui.springcore.example.resource;
public class LoadResourceBean implements ResourceLoaderAware{
@Autowired
ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
System.out.println("Is ApplicationContext? " + (this.resourceLoader == resourceLoader));
Resource res = this.resourceLoader.getResource("classpath:extend.properties");
System.out.println("Bean load Resource :" + res);
}
}和普通的Bean一样,还可以通过构造方法和setter方法注入ResourceLoader。
XML配置获取资源
我们可以直接在XML中指定资源路径,然后在setter或构造方法中获取到对应的资源,看下面的例子。
XMLConfigBean的Set方法直接获取一个
package chkui.springcore.example.hybrid.resource;
public class XMLConfigBean {
public void setResource(Resource res) throws IOException {
System.out.println("XML load Resource :" + res);
Properties p = new Properties();
p.load(res.getInputStream());
System.out.println("Properties Info: " + p.getProperty("info"));
}
}我们只需要在XML配置文件中指定资源路径位置,Spring会自动帮我们完成转换:
<beans>
<bean class="chkui.springcore.example.hybrid.resource.XMLConfigBean">
<property name="resource" value="classpath:extend.properties"/>
</bean>
</beans>在XMLConfigBean::setResource方法中我们拿到的是"classpath:extend.properties"这一项资源。
通配符指定资源
除了使用指定固定路径的方式获取一项资源,我们还可以使用"?"、"*"等通配符使用匹配规则来获取资源,例如:
Resource[] resList = ctx.getResources("classpath:hybrid/**/*.xml");Spring官网将这种资源匹配规则称为“Ant-style匹配”,虽然并不知道源自什么地方(应该是源自
- "?":匹配一个字符。例如"classpath:conf?g.xml"匹配"classpath:config.xml"也匹配"classpath:conf1g.xml"但是不匹配"classpath:conf12g.xml"。
- "*":匹配0到多个字符。例如"classpath:*.xml"匹配classpath根目录下所有.xml文件。而"classpath:config/*.xml"匹配config文件夹中所有.xml文件。
- "**":匹配0到多个目录。例如"classpath:**/*.xml"匹配整个classpath下所有*.xml文件。"classpath:config/**/*.xml"匹配config文件夹以及所有子文件夹的.xml文件。
- {arg1:{a-z}+}:匹配任意多个a-z的字符,并将匹配到的内容赋值到变了arg1中。该条规则实用于AntPathMatcher,当无法在ApplicationContext的资源匹配规则中使用。
classpath*:扩展
在通配符的基础上,spring扩展了一个
对于一个运行的Jvm来说,classpath的“根目录”一般有多个。比如在当前开发的工程有一个包含main方法的类文件——chkui/example/spinrg/app.class,此时引入一个jar包也包含一个一样的类文件chkui/example/spring/app.class(有空的码友可以自己试试Jvm到底运行哪个)。这种情况对于Jvm来说就引出"多个classpath"和"首选classpath"的概念,而classpath:和classpath*的差异就是,前者从首选classpath中优先获取资源,而后者会从所有classpath中寻找资源。而首先classpath一般是我们当前工程的编译文件(案例代码在
其实在Jvm的资源加载方式上已经对
为了演示这个过程我们引入了Google的Guava包(因为整个工程都没用到guava的内容,所以修改他的类不会产生影响),然后对应的在自己的工程中增加一个Guava包中相同的package和类:
package com.google.common.base;
public final class Preconditions {}在编译之后,会在bin文件夹(如果是maven就是/target)中产生一个main/com/google/common/base/Preconditions.class文件。然后通过下面的代码测试资源加载:
public static void multiResourceLoad() throws IOException {
final String classPath = "com/google/common/base/Preconditions.class";
//class.getResource需要使用"/"表示root路径
//首选路径的资源
print("classpath: ", ResourceApp.class.getResource("/" + classPath));
//Verify没有被覆盖,输出Jar包中的内容,注意jar:file: 协议的格式
print("In Jar classpath: ", ResourceApp.class.getResource("/" + unMultiClassPath));
//ClassLoader::getResource获取首选路径资源
print("First classpath: ", Verify.class.getClassLoader().getResource(classPath));
//ClassLoader::getResources获取所有资源
Enumeration<URL> e = ResourceApp.class.getClassLoader().getResources(classPath);
int count = 1;
while (e.hasMoreElements()) {
URL url = e.nextElement();
print("classpath*[", count++ ,"]:", url);
}
}运行之后,只有在最后的迭代器中输出了Guava包中的Preconditions.class的路径,而其余位置都输出的是我自行创建的Preconditions.class,也就是首选classpath下的Preconditions.class,首选的资源也就是ClassLoader::getResources获取的迭代器的第一个值。
Spring的
public static void multiResourceLoad(ApplicationContext ctx){
ApplicationContext ctx = new AnnotationConfigApplicationContext();
final String classPath = "com/google/common/base/Preconditions.class";
final String unMultiClassPath = "com/google/common/base/Verify.class";
print("classpath: ", Arrays.asList(ctx.getResources("classpath:" + classPath)));
print("classpath*: ", Arrays.asList(ctx.getResources("classpath*:" + classPath)));
print("unmulti-classpath*: ", Arrays.asList(ctx.getResources("classpath*:" + unMultiClassPath)));
}Spring中的各项资源
不仅仅是ApplicationContext::getResources方法,实际上Spring中绝大部分外部资源加载都是通过前面介绍的规则使用同一个工具类完成的,所以我们可以在许多地方使用对应的"协议"来管理我们的资源,比如下面的例子:
@ImportResource("classpath:hybrid/resource/config-*.xml")
public class ResourceApp {}