java resouce_深入了解 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() {

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目录如下:

eead9ac9f4c3ced68955c77c6bd57ea2.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值