您的位置:首页 > 编程语言 > Java开发

深入了解 Java Resource && Spring Resource

2019-11-11 17:23 2036 查看

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()
获取的

在平时使用的过程中,有三点需要注意:

  1. classpath 和 classpath* 区别:

    classpath:只会返回第一个查找到的文件
    classpath*:会返回所有查找到的文件

  2. Spring
    中,需要直接表示使用
    ClassPathResource()
    来查找的话,可以直接添加
    classpath:

  3. 使用

    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进阶的文章,感谢关注

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: