Spring核心——资源管理 原

Resource——资源

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

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

(文中的代码仅仅用于说明问题,源码请到案例gitee库下载,对应的代码在包chkui.springcore.example.hybrid.resource中。)

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也可以通过域名规则来获取网络上的资源。

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

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的整个生态或者容器,你也可以将其资源管理作为一个工具整合到自己的系统中而提高效率。它扩展了以下内容:

  1. 隐藏底层实现。对于各种各样的资源Spring都使用了不同的实现类来管理,但是他利用适配器模式让使用者仅仅需要了解org.springframework.core.io.Resource接口即可。
  2. 新增资源存在判断、资源操作权限相关的功能,相对于java.net.URL资源不存在则设置为null更友好。
  3. 支持通配符来获取资源,例如 :classpath:a/b/**/applicationContext-*.xml。

协议与路径

在前面的内容中就提到了多个协议,spring的资源管理功能除了标准的协议,还增加了一个——classpath:协议,他表示从当前的classpath根路径开始获取资源。对于Spring的资源管理功能而言,主要有以下几种协议:

协议

例子

说明

classpath:

classpath:res/extend.properties

从当前jvm的classpath根路径开始获取资源。

file:

file:///tmp/myfile.data

从操作系统(文件路径)的路径获取资源。

http(s):

http(s)://www.chkui.com/

从互联网获取资源。

(无标记)

/data/extend.data

根据应用上下文获取资源。

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

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

ApplicationContext ctx = new ClassPathXmlApplicationContext("config/ctx.xml");

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

ApplicationContext ctx = new ClassPathXmlApplicationContext("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接口,所以我们通常使用以下方法获取资源:

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方法直接获取一个Resource

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匹配”,虽然并不知道源自什么地方(应该是源自Apache Ant项目,但是我在Ant项目文档中还没看到对应的说明,心细致的码友可以再找找),但是Spring官方文档对其有详细的说明,详见AntPathMatcher的说明。Ant-style的匹配规则大致如下:

  1. "?":匹配一个字符。例如"classpath:conf?g.xml"匹配"classpath:config.xml"也匹配"classpath:conf1g.xml"但是不匹配"classpath:conf12g.xml"
  2. "*":匹配0到多个字符。例如"classpath:*.xml"匹配classpath根目录下所有.xml文件。而"classpath:config/*.xml"匹配config文件夹中所有.xml文件。
  3. "**":匹配0到多个目录。例如"classpath:**/*.xml"匹配整个classpath下所有*.xml文件。"classpath:config/**/*.xml"匹配config文件夹以及所有子文件夹的.xml文件。
  4. {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下的资源。

下面的代码展示了这个过程,案例代码在chkui.springcore.example.hybrid.resource.ResourceApp::multiResourceLoad方法中。

为了演示这个过程我们引入了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的classpath*:协议实际上底层也是用ClassLoader::getResources的方式实现的,不过扩展了支持通配符并将资源转换为org.springframework.core.io.Resource。上面用JDK演示的代码用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 {}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我的博客

安卓开发之调试程序

一、DDMS中LogCat(这样就能调试程序了) 代码下方显示LogCat,windows/show view/other选中logcat点击ok就会在代码下面...

36180
来自专栏实战docker

修改,编译,GDB调试openjdk8源码(docker环境下)

在上一章《在docker上编译openjdk8》里,我们在docker容器内成功编译了openjdk8的源码,有没有读者朋友产生过这个念头:“能不能修改open...

59290
来自专栏坚毅的PHP

jersey处理支付宝异步回调通知的问题:java.lang.IllegalArgumentException: Error parsing media type 'application/x-www

tcpflow以流为单位分析请求内容,非常适合服务器端接口类服务查问题 这次遇到的问题跟支付宝支付后的回调post结果有关 淘宝的代码例子: publi...

62050
来自专栏木木玲

Netty 那些事儿 ——— 心跳机制

82390
来自专栏JavaEdge

从servlet容器说起1 Servlet容器的启动过程2 Web 应用的初始化工作

364120
来自专栏好好学java的技术栈

看了这篇文章,mybatis配置你肯定会了

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。

10230
来自专栏Android 研究

OKHttp源码解析(三)--中阶之线程池和消息队列

android的异步任务一般都是用Thread+Handler或者AsyncTask来实现,其中笔者当初经历过各种各样坑,特别是内存泄漏,当初笔者可是相当的欲死...

34440
来自专栏岑玉海

Hadoop源码系列(一)FairScheduler申请和分配container的过程

1、如何申请资源 1.1 如何启动AM并申请资源 1.1.1 如何启动AM val yarnClient = YarnClient.createYarnClie...

45840
来自专栏一个爱瞎折腾的程序猿

asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档

开局一张图,然后开始编,一些基本的asp.net core东西就不再赘述,本文只对Swashbuckle.AspNetCore的几个使用要点进行描述。

19010
来自专栏流媒体人生

Windows CE 系统进程外组件应用开发

这篇文章说明了如何使用 WindowS CE 6.0 系统的 DCOM ,开发 com 服务进程。向客户端提供跨进程的 COM 组件服务,以及自定义接口代理 /...

11820

扫码关注云+社区

领取腾讯云代金券