Spring核心——资源管理

Resource——资源

对于一个联机事务型系统(业务系统)来说,所依赖的外部运行信息主要有2个来源:数据项资源项。数据项的存放位置通常是使用各种关系性或NoSql数据库,而资源项通常是使用文件、网络信息的方式来存储。

早在JDK1.0的时代Java就已经提供了本地资源和网络资源的读取功能——java.net.URL。他可以同时管理本地资源(操作系统资源)以及网络资源,如下面这个例子:

publicclassResourceApp{publicstaticvoidmain(String[] args)throwsMalformedURLException{//读取本地资源URL url = ResourceApp.class.getResource("/extend.properties");print(url);//读取互联网资源url =newURL("http","www.baidu.com",80,"");print(url);url =newURL("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也可以通过域名规则来获取网络上的资源。

注意输出内容中的开头file: 、http:以及https:,他们表示资源的协议,除了以上这三者,还有ftp:、mailto:等协议。关于URL的详细解释可以看ITEF标准

URL指向某一个资源之后,可以使用URL::openStream或URL::getFile等方法进一步获取文件中的内容:

publicclassResourceApp{publicstaticvoidmain(String[] args)throwsMalformedURLException{Url url =newURL("https","www.chkui.com",443,"/174870bb04.js");try(InputStream is = url.openStream()){byte[] buffer =newbyte[1024*1024];is.read(buffer);String content =newString(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根路径开始获取资源。对于Spring的资源管理功能而言,主要有以下几种协议:

classpath:file:http(s):这三个协议都很明确的指明了获取资源的路径,但是没有声明协议的情况就比较特殊,他需要根据上下文来判定适用的路径。

上下文与IoC这篇文章中已经介绍过,经过层层继承和实现,Spring提供容器实现功能的主要是ClassPathXmlApplicationContextFileSystemXmlApplicationContext两个类,这两个Context本质上都是实现了相同的Context功能,最明显的区别之一就是加载文件的路径不同。比如下面的情况:

ApplicationContext ctx =newClassPathXmlApplicationContext("config/ctx.xml");

ClassPathXmlApplicationContext默认启用的是ClassPathResource来管理资源,所以上面的路径配置相当于"classpath:config/ctx.xml"。但是如果修改为以下形式:

ApplicationContext ctx =newClassPathXmlApplicationContext("file:///config/ctx.xml");

通过协议明确告知路径规则,那么在ApplicationContext会使用对应的FileSystemResource来加载管理资源。

FileSystemXmlApplicationContextClassPathXmlApplicationContext相互对应——默认使用的FileSystemResource,可以通过声明协议来指定对应的资源加载类。

上面的内容提到了ClassPathResource和FileSystemResource。Spring为不同类型、协议的资源指定了各种各种的org.springframework.core.io.Resource实现类,主要有UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource。从字面上看大概能了解对应的功能。在使用的时候我们并不需要了解他们的具体实现,只要知道不同的协议对应的资源路径即可。

获取资源的方法

直接使用ApplicationContext

在明确所支持的协议之后,我们就可以用ResourcePatternResolver::getResources方法来获取资源。ApplicationContext继承了ResourcePatternResolver接口,所以我们通常使用以下方法获取资源:

packagechkui.springcore.example.resource;publicclassResourceApp{publicstaticvoidmain(String[] args){ApplicationContext ctx =newAnnotationConfigApplicationContext(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的方式来获取资源加载接口:

packagechkui.springcore.example.resource;publicclassLoadResourceBeanimplementsResourceLoaderAware{@OverridepublicvoidsetResourceLoader(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):

packagechkui.springcore.example.resource;publicclassLoadResourceBeanimplementsResourceLoaderAware{@AutowiredResourceLoader resourceLoader;@OverridepublicvoidsetResourceLoader(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方法直接获取一个Resource

packagechkui.springcore.example.hybrid.resource;publicclassXMLConfigBean{publicvoidsetResource(Resource res)throwsIOException{System.out.println("XML load Resource :"+ res);Properties p =newProperties();p.load(res.getInputStream());System.out.println("Properties Info: "+ p.getProperty("info"));}}

我们只需要在XML配置文件中指定资源路径位置,Spring会自动帮我们完成转换:

在XMLConfigBean::setResource方法中我们拿到的是"classpath:extend.properties"这一项资源。

通配符指定资源

除了使用指定固定路径的方式获取一项资源,我们还可以使用"?"、"*"等通配符使用匹配规则来获取资源,例如:

Resource[] resList = ctx.getResources("classpath:hybrid/**/*.xml");

Spring官网将这种资源匹配规则称为“Ant-style匹配”,虽然并不知道源自什么地方(应该是源自Apache Ant项目,但是我在Ant项目文档中还没看到对应的说明,心细致的码友可以再找找),但是Spring官方文档对其有详细的说明,详见AntPathMatcher的说明。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扩展了一个classpath*:协议。

对于一个运行的Jvm来说,classpath的“根目录”一般有多个。比如在当前开发的工程有一个包含main方法的类文件——chkui/example/spinrg/app.class,此时引入一个jar包也包含一个一样的类文件chkui/example/spring/app.class(有空的码友可以自己试试Jvm到底运行哪个)。这种情况对于Jvm来说就引出"多个classpath"和"首选classpath"的概念,而classpath:和classpath*的差异就是,前者从首选classpath中优先获取资源,而后者会从所有classpath中寻找资源。而首先classpath一般是我们当前工程的编译文件(案例代码在[project-root]/bin/main)。

其实在Jvm的资源加载方式上已经对classpath:classpath*:提供了不同的实现,但是理解起来比较“绕”。一般情况下我们使用Class::getResource都是获取首选classpath路径下的资源,而使用ClassLoader::getResources(classPath)可以获取所有classpath下的资源。

为了演示这个过程我们引入了Google的Guava包(因为整个工程都没用到guava的内容,所以修改他的类不会产生影响),然后对应的在自己的工程中增加一个Guava包中相同的package和类:

packagecom.google.common.base;publicfinalclassPreconditions{}

在编译之后,会在bin文件夹(如果是maven就是/target)中产生一个main/com/google/common/base/Preconditions.class文件。然后通过下面的代码测试资源加载:

publicstaticvoidmultiResourceLoad()throwsIOException{finalString 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 e = ResourceApp.class.getClassLoader().getResources(classPath);intcount =1;while(e.hasMoreElements()) {URL url = e.nextElement();print("classpath*[", count++ ,"]:", url);}}

运行之后,只有在最后的迭代器中输出了Guava包中的Preconditions.class的路径,而其余位置都输出的是我自行创建的Preconditions.class,也就是首选classpath下的Preconditions.class,首选的资源也就是ClassLoader::getResources获取的迭代器的第一个值。

Spring的classpath*:协议实际上底层也是用ClassLoader::getResources的方式实现的,不过扩展了支持通配符并将资源转换为org.springframework.core.io.Resource。上面用JDK演示的代码用spring的资源管理实现为下面的形式:

publicstaticvoidmultiResourceLoad(ApplicationContext ctx){    ApplicationContext ctx =newAnnotationConfigApplicationContext();finalString classPath ="com/google/common/base/Preconditions.class";finalString 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")publicclassResourceApp{}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容