深入了解 Java Resource && Spring Resource
在Java
中,为了从相对路径读取文件,经常会使用的方法便是:
xxx.class.getResource();
xxx.class.getClassLoader().getResource();
在Spring
中,我们还可以通过Spring
提供的Resource
进行一些操作:
ClassPathResource
FileSystemResource
ServletContextResource
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
这里简单总结下他们的区别:
ClassLoader##getResource()
这个方法是今天的主角。
我们都知道ClassLoader
的作用是用来加载.class
文件的,并且ClassLoader
是遵循Java
类加载中的双亲委派机制的。
那么,ClassLoader
是如何找到这个.class
文件的呢?答案是URLClassPath
Java
中自带了3个ClassLoader
分别是BootStrap ClassLoader
,EtxClassLoader
,AppClassLoader
,
这3个ClassLoader
都继承自URLClassLoader
,而URLClassLoader
中包含一个URLClassPath
用来记录每个ClassLoader
对应的加载.class
文件的路径,当需要加载资源的时候,只管从URLClassPath
对应的路径查找即可。
下面是测试代码:
System.out.println("BootStrap ClassLoader ");
Stream.of(System.getProperty("sun.boot.class.path").split(";")).forEach(System.out::println);
System.out.println("ExtClassLoader:");
Stream.of(System.getProperty("java.ext.dirs").split(";")).forEach(System.out::println);
System.out.println("AppClassLoader:");
Stream.of(System.getProperty("java.class.path").split(";")).forEach(System.out::println);
输出如下:
BootStrap ClassLoader
H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
H:\java\jdk1.8\jre\lib\sunrsasign.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\classes
ExtClassLoader:
H:\java\jdk1.8\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
AppClassLoader:
H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\deploy.jar
H:\java\jdk1.8\jre\lib\ext\access-bridge-64.jar
H:\java\jdk1.8\jre\lib\ext\cldrdata.jar
H:\java\jdk1.8\jre\lib\ext\dnsns.jar
H:\java\jdk1.8\jre\lib\ext\jaccess.jar
H:\java\jdk1.8\jre\lib\ext\jfxrt.jar
H:\java\jdk1.8\jre\lib\ext\localedata.jar
H:\java\jdk1.8\jre\lib\ext\nashorn.jar
H:\java\jdk1.8\jre\lib\ext\sunec.jar
H:\java\jdk1.8\jre\lib\ext\sunjce_provider.jar
H:\java\jdk1.8\jre\lib\ext\sunmscapi.jar
H:\java\jdk1.8\jre\lib\ext\sunpkcs11.jar
H:\java\jdk1.8\jre\lib\ext\zipfs.jar
H:\java\jdk1.8\jre\lib\javaws.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\lib\jfxswt.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\management-agent.jar
H:\java\jdk1.8\jre\lib\plugin.jar
H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
F:\spring-test\target\classes
AppClassLoader
负责常用的JDK jar
以及项目所依赖的jar
包上述参数可以通过 sun.misc.Launcher.class获得
通过输出的参数,我们可以清晰的看出来各个
ClassLoader
负责的区域
说了这么多,这个和ClassLoader#getResource()
有什么关系呢?
关系很大,前面刚刚提问过,ClassLoader
是如何读取.class
文件的呢?
答案是URLClassPath#getResource()
方法:每个UrlClassLoader
都是通过URLClassPath
来存储对应的加载区域,当需要查找.class
文件的时候,就通过URLClassPath#getResource()
查找即可。
下面再来看看ClassLoader#getResource()
//双亲委派查找
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
//由于BootStrap ClassLoader是C++写的,Java拿不到其引用。
//因此这里单独写了一个方法获取BootStrapResource()
private static URL getBootstrapResource(String name) {
URLClassPath ucp = getBootstrapClassPath();
Resource res = ucp.getResource(name);
return res != null ? res.getURL() : null;
}
URLClassLoader#findResource()
public URL findResource(final String name) {
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
我们只用注意这一句ucp.findResource(name, true);
,这边是查找.class
文件的方法,因此我们可以总结出通过ClassLoader#getResource()
的流程:
- 首先,
AppClassLoader
委派给ExtClassLoader
查找是否存在对应的资源 ExtClassLoader
委派给BootStrap ClassLoader
查找是有存在对应的资源BootStrap ClassLoader
通过URLClasspath
查找自己加载的区域,查找到了即返回BootStrap ClassLoader
未查找到对应资源,ExtClassLoader
通过URLClasspath
查找自己加载的区域,查找到了即返回ExtClassLoader
未查找到对应资源,AppClassLoader
通过URLClasspath
查找自己加载的区域,查找到了即返回AppClassLoader
未查找到,抛出异常。
这个过程,就和双亲委派模型加载.class
文件的过程一样。
在这里我们就可以发现,通过ClassLoader#getResource()
可以获取JDK
资源,所依赖的JAR
包资源等
因此,我们甚至可以这样写:
//读取`java.lang.String.class`的字节码
InputStream inputStream =Test.class.getClassLoader().getResourceAsStream("java/lang/String.class");
try(BufferedInputStream bufferedInputStream=new BufferedInputStream(inputStream)){
byte[] bytes=new byte[1024];
while (bufferedInputStream.read(bytes)>0){
System.out.println(new String(bytes, StandardCharsets.UTF_8));
}
}
明白了ClassLoader#getResource()
,其实本篇文章就差不多了,因为后面要将的几个方法,底层都是ClassLoader#getResource()
class##getResource()
class##getResource()
底层就是ClassLoader#getResource()
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
不过有个小区别就在于class#getResource()
多了一个resolveName()
方法:
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
这个resolveName()
大致就是判断路径是相对路径还是绝对路径,如果是相对路径,则资源名会被加上当前项目的根路径:
Test.class.getResource("spring-config.xml");
resolve之后变成
com/dengchengchao/test/spring-config.xml
这样的资源就只能在当前项目中找到。
Test.class.getResource("test.txt"); //相对路径
Test.class.getResource("/"); //根路径
注意:
ClassLoader#getResource()
不能以/
开头
Spring # ClassPathResource()
在Spring
中,对Resource
进行了扩展,使得Resource
能够适应更多的应用场景,
不过ClssPathResource()
底层依然是ClassLoader##getResource()
,因此ClassLoader##getResource()
d的特性,ClassPathResource
也支持。
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
} else {
return this.classLoader != null ? this.classLoader.getResource(this.path) : ClassLoader.getSystemResource(this.path);
}
}
ClassPathResource
用于读取classes
目录文件
一般来说,对于SpringBoot
项目,打包后的项目结构如下:
|-- xxx.jar
|--- BOOT-INF
|--------|--classes
|--------|----|--com
|--------|----|-- application.properties
|--------|----|--logback.xml
| -------|-- lib
|--- META-INF
|--- org
可以看到,ClassPathResource()
的起始路径便是classes
,平时我们读取的application.properties
便是使用ClasspathResource()
获取的
在平时使用的过程中,有三点需要注意:
classpath 和 classpath* 区别:
classpath:只会返回第一个查找到的文件
classpath*:会返回所有查找到的文件在
Spring
中,需要直接表示使用ClassPathResource()
来查找的话,可以直接添加classpath:
头使用
classpath
以/
和不以/
开头没有区别
Spring # ServletContextResource
ServletContextResource
是针对Servlet
来做的,我们知道,Servlet
规定webapp
目录如下:
而ServletContextResource
的路径则是xxx
目录下为起点。也就是可以通过ServletContextResource
获取到form.html
等资源。
同时对比上面的ClassPathResource
我们可以发现:
"classpath:com"
等价于:
ServletContextResource("WEB-INF/classes/com")
Spring # FileSystemResource
FileSystemResource
没什么好说的,就是系统目录资源,比如
ApplicationContext ctx =
new FileSystemXmlApplicationContext("D://test.xml");
它的标记头为file:
例如:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("flie:D://test.xml");
如果觉得写得不错,欢迎关注微信公众号:逸游Java ,每天不定时发布一些有关Java进阶的文章,感谢关注
深入了解 Java Resource && Spring Resource的更多相关文章
- nested exception is java.io.FileNotFoundException: class path resource [spring/spring-datasource-mog
spring单元測试时发现的问题: org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsin ...
- Caused by: java.io.FileNotFoundException: class path resource [spring/springmvc.xml] cannot be opene
Caused by: java.io.FileNotFoundException: class path resource [spring/springmvc. ...
- 基础篇:JAVA资源之IO、字符编码、URL和Spring.Resource
目录 1 JAVA.IO字节流 2 JAVA.IO字符流 3 乱码问题和字符流 4 字符集和字符编码的概念区分 5 URI概念的简单介绍 6 URL概念及与URL的区别 7 Spring.Resour ...
- Spring resource bundle多语言,单引号format异常
Spring resource bundle多语言,单引号format异常 前言 十一假期被通知出现大bug,然后发现是多语言翻译问题.法语中有很多单引号,单引号在format的时候出现无法匹配问题. ...
- Spring @Resource注解
@Resource注解 @Resource 注解被用来激活一个命名资源(named resource)的依赖注入,在JavaEE应用程序中,该注解被典型地转换为绑定于JNDI context中的一 ...
- spring 编译时抱错纪录class path resource [spring/] cannot be resolved to URL because it does not exist
class path resource [spring/] cannot be resolved to URL because it does not exist; 在 pom.xml 里添加如下代码 ...
- org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [spring/applicationContext-service.xml]: Cannot resolve refer
<!-- aop --> <aop:config> <aop:pointcut expression="execution(* com.zsn.Service. ...
- Spring Resource框架体系介绍
Resource介绍 在使用spring作为容器进行项目开发中会有很多的配置文件,这些配置文件都是通过Spring的Resource接口来实现加载,但是,Resource对于所有低级资源的访问都不够充 ...
- java javax.annotation.Resource注解的详解
转自:https://www.jb51.net/article/95456.htm java 注解:java javax.annotation.Resource 当我们在xml里面为类配置注入对象时 ...
随机推荐
- spring源码分析系列4:ApplicationContext研究
ApplicationContext接口 首先看一下一个最基本的上下文应该是什么样子 ApplicationContext接口的注释里写的很清楚: 一个基本applicationContext应该提供 ...
- 通过搭建MySQL掌握k8s(Kubernetes)重要概念(上):网络与持久卷
上一篇"通过实例快速掌握k8s(Kubernetes)核心概念"讲解了k8s的核心概念,有了核心概念整个骨架就完整了,应付无状态程序已经够了,但还不够丰满.应用程序分成两种,无状态 ...
- Windows10 1903错误0xc0000135解决方案
Windows10 1903错误0xc0000135解决方案 === windows10 1903是2019年Mircosoft推出的最新版windows操作系统,但是有很多机器装上之后会存在不支持. ...
- redis的安装与五种结构的使用
这次我们来说说我们的redis,在我们的redis的认知里,最熟悉的就是用redis作为缓存使用,还有我们的分布式session,其实还有很多redis的使用,还有redis的哨兵模式等等. Redi ...
- Kafka安装-Linux版
1. 准备 1.1 Kafka Kafka版本采用0.10.2.1,下载0.10.2.1文件 http://kafka.apache.org/downloads 1.2 JDK JDK ...
- 题解:2018级算法第二次上机 Zexal的竞赛
题目描述: 样例: 实现解释: 一道需要一点思考的动态规划题目 知识点:动态规划,数据记录 首先将题目描述调整:分别输入不同分数的题目总分(便于后续计算),当获得了i分数的总分后无法获得i-1和i+1 ...
- 01 【PMP】组织结构类型
[PMP]组织结构类型 1.简单型 描述:人员并肩工作,所有者/经营者直接做出主要决定并监督执行. PM角色:兼职(协调员) PM权限:极少(无) 项目管理人员:极少(无) 资源可用性:极少(无) ...
- Mongoose-modified-at 时间自动记录插件介绍
Mongoose-modified-at 是一款自动更新字段变化时间并记录到数据库中的 Mongoose 插件,类似 Mongoose 自带的 timestamps 功能. 使用场景 让我们考虑一个场 ...
- vue系列---响应式原理实现及Observer源码解析(一)
_ 阅读目录 一. 什么是响应式? 二:如何侦测数据的变化? 2.1 Object.defineProperty() 侦测对象属性值变化 2.2 如何侦测数组的索引值的变化 2.3 如何监听数组内容的 ...
- CodeForces - 1214D D. Treasure Island
题目链接:https://vjudge.net/problem/2728294/origin 思路:可以抽象成管道,先试试能不能找到一个通道能通到终点, 如果可以则封锁这个通道,一个石头即可, 再试试 ...