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

深入了解 Java Resource && Spring Resource的更多相关文章

  1. nested exception is java.io.FileNotFoundException: class path resource [spring/spring-datasource-mog

    spring单元測试时发现的问题: org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsin ...

  2. Caused by: java.io.FileNotFoundException: class path resource [spring/springmvc.xml] cannot be opene

                        Caused by: java.io.FileNotFoundException: class path resource [spring/springmvc. ...

  3. 基础篇:JAVA资源之IO、字符编码、URL和Spring.Resource

    目录 1 JAVA.IO字节流 2 JAVA.IO字符流 3 乱码问题和字符流 4 字符集和字符编码的概念区分 5 URI概念的简单介绍 6 URL概念及与URL的区别 7 Spring.Resour ...

  4. Spring resource bundle多语言,单引号format异常

    Spring resource bundle多语言,单引号format异常 前言 十一假期被通知出现大bug,然后发现是多语言翻译问题.法语中有很多单引号,单引号在format的时候出现无法匹配问题. ...

  5. Spring @Resource注解

    @Resource注解   @Resource 注解被用来激活一个命名资源(named resource)的依赖注入,在JavaEE应用程序中,该注解被典型地转换为绑定于JNDI context中的一 ...

  6. 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 里添加如下代码 ...

  7. 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. ...

  8. Spring Resource框架体系介绍

    Resource介绍 在使用spring作为容器进行项目开发中会有很多的配置文件,这些配置文件都是通过Spring的Resource接口来实现加载,但是,Resource对于所有低级资源的访问都不够充 ...

  9. java javax.annotation.Resource注解的详解

    转自:https://www.jb51.net/article/95456.htm java 注解:java javax.annotation.Resource  当我们在xml里面为类配置注入对象时 ...

随机推荐

  1. <<Java并发编程的艺术>>-阅读笔记和思维导图

    最近在坚持每天阅读<>,不但做好笔记(MarkDown格式),还做好思维导图. 如果大家感兴趣,可以可以到码云上阅读笔记和到ProcessOn上阅读思维导图. 码云:https://git ...

  2. tesseract 测试样例

    该图片的链接为https://raw.githubusercontent.com/Python3WebSpider/TestTess/master/image.png,可以直接保存或下载. 首先用命令 ...

  3. SQL创建和调用有默认值的存储过程

    先创建一个有默认值的存储过程 create procedure usp_unpass ), --有默认值的参数放到最后 as select @score go 该存储过程有2个参数 ,其中@score ...

  4. Failure to transfer org.springframework:spring-jcl:jar:5.0.7.RELEASE from

    错误信息: Failure to transfer org.springframework.boot:spring-boot-maven-plugin:pom:1.5.4.RELEASE from h ...

  5. PowerBI系列之入门案例动态销售报告

    本文将讲解如何从零开始使用PowerBI Desktop制作一份动态销售报告.帮助大家快速入门PowerBI Desktop的操作.我们先来看一下一份动态销售报告的构成. 1.左上角放置了小黎子数据分 ...

  6. 攻防世界(XCTF)WEB(进阶区)write up(三)

    挑着做一些好玩的ctf题 FlatScience web2 unserialize3upload1wtf.sh-150ics-04web i-got-id-200 FlatScience 扫出来的lo ...

  7. Web渗透之mssql差异备份getshell

    简介 差异备份数据库得到webshell.在sql server 里dbo和sa权限都有备份数据库权限,我们可以把数据库备份称asp文件,这样我们就可以通过mssqlserver的备份数据库功能生成一 ...

  8. windows系统安全日志取证工具

    0x01 关于日志 Windows安全事件日志中详细记录了是谁在什么时候通过什么手段登录到系统或者注销了登录,通过分析该日志可以详细了解服务器的安全情况以及必要时的取证工作. 0x02 查看日志 传统 ...

  9. Unity - HasExitTime用法

    本文详细分析了AnimatorController中动画切换过渡问题,即Translation过渡及hasExitTime的问题.方法为对实际项目中的所有情况进行分类,规划逻辑图,可视化分析解决这些问 ...

  10. 玩转 RTC时钟库 DS1302

    1.前言     最近博主在弄8266编程的时候,偶然发现两个全新时钟模块压仓货: DS1302 DS3231     为了避免资源浪费以及重复编写代码,博主还是抱着尝试的心态去寻找能够同时兼容 DS ...