深入了解 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()获取的
在平时使用的过程中,有三点需要注意:
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进阶的文章,感谢关注
相关文章推荐
- Spring学习(14)--- 基于Java类的配置Bean 之 @ImportResource & @Value 注解
- [jvm]深入JVM(一):从"abc"=="abc"看java的连接过程
- 深入了解Java ClassLoader(转帖)
- 深入了解Java运行时的内存区域
- 主题: 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java的String
- 深入理解spring注解(1)java注解基础
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java的String
- 面向 Java 开发人员的 Scala 指南: 深入了解 Scala 并发性
- java serializable深入了解
- 深入了解java运行时的内存区域
- java serializable深入了解
- 深入了解android平台的jni---本地多线程调用java代码
- <bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- 相对路径获取JAVA配置文件Class.getResourceAsStream() & ClassLoader的getResourceAsStream()
- java中的构造方法的深入了解
- java.util.Properties & java.util.ResourceBundle
- Java中"异常机制"的深入研究