读取文件的几个方法

在SpringBoot中,比较常见的几个读取文件的方法有

  • Classloader.getResourceAsStream()
  • ResourceUtils.getFile
  • ClassPathResource读取
  • new PathMatchingResourcePatternResolver() 读取

外部表现形式虽然有所差异,内在方式还是以 Classloader.getResourceAsStream 为唯一标杆,其余三个都是针对它的一个封装。

classloader 读取又是在classpath的划定范围内进行读取的

Classloader.getResourceAsStream()

classloader内部都是依赖 ucp 实现的, URLClassPath ,加载的URL的一个集合,URL是在实例化Classloader传递进去的,发现在 ucp 中读取文件其实是通过一些协议去读取文件内容的,如果是文件系统则是file协议,如果http开头的则是通过http协议。

在一些常见的攻击场景中,攻击者通常会注入一些第三方jar包到我们系统当中,这个第三方jar包就是通过http的方式进行加载的,如果我们保护系统不加载第三方jar包,可以通过字节码的技术在ucp加载jar包的时候判断如果是http协议的,就提示告警或者是加白名单..

ResourceUtils.getFile

这个方法我最先用在加载 log4j.xml 上边,经常出现

  1. log4j.xml cannot be resolved to absolute file path because it does not exist

经过多次对源码进行观摩,发现它有2种读取方式,分别是

  • 以classpath的方式进行读取,该方式扫描classpath,利用的是classloader寻找资源的能力
  • 以绝对路径的方式进行,利用的是文件系统的能力

classpath: 开头走classpath (内部会进行一次subString移除classpath:), 其他走文件系统

ClassPathResource读取

  1. public ClassPathResource(String path, ClassLoader classLoader) {
  2. this.path = path;
  3. this.classLoader = classLoader;
  4. }

这个的定义就很明确,扫描classpath获取对应资源,缺点是只能获取一个。
虽然定义很明确,但是 ClassPath 本身就让人犯嘀咕,到底带不带 classpath: , 结论是不要带,因为它是根据给定的文件,到classpath目录下进行查找。

我的classpath目录是 /Users/chenshun/open/demo/target/classes , 给我一个 classpath:log.txt. 两个拼凑的路径是 /Users/chenshun/open/demo/target/classes/claspath:log.txt 自然是没有的

PathMatchingResourcePatternResolver 进行读取

相比上边只能搞一个,这个就高级多了,用了ant风格进行正则匹配。支持搞多个文件,同时也引入了一个 classpath*: 的概念

  • classpath:/mapper/.xml
    • 扫描classes和所有的jar包符合该规则的文件,利用了classloader加载资源的能力
  • classpath:/mapper/.xml 或者是 classpath:mapper/.xml
    • 扫描classpath所有符合该规则的文件,利用ClassPathResource定位rootDir, 扫描rootDir的文件
  • classpath:/mapper/a.xml 或者是 classpath:mapper/a.xml
    • 去掉classpath 实现同下
  • no classpath (内部ClassPathResource实现)
    • /mapper/a.xml
    • mapper/a.xml

总结

总结了 classloader是如何读取文件的,以及在Spring中各个加载文件的方法和class的使用方式。

SpringBoot的启动方式和classloader

上边讲述了读取文件的方式本质都是利用了 classloader 扫描classpath加载文件的能力,在Spring的启动方式中,它的classloader又存在不一样。如果不明白这一点,就很容易弄出一些乌龙。

IDEA main方法启动

  1. idea中启动的时候,可以看到idea是利用 -classpath 指定jar包进行启动的,指定了它的classpath, 而且它的classloderappclassloader
  1. /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=62066:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar:/Users/chenshun/open/demo/target/classes:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.5.3/spring-boot-starter-web-2.5.3.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-starter/2.5.3/spring-boot-starter-2.5.3.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot/2.5.3/spring-boot-2.5.3.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.5.3/spring-boot-autoconfigure-2.5.3.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.5.3/spring-boot-starter-logging-2.5.3.jar:/Users/chenshun/.m2/repository/ch/qos/logback/logback-classic/1.2.4/logback-classic-1.2.4.jar:/Users/chenshun/.m2/repository/ch/qos/logback/logback-core/1.2.4/logback-core-1.2.4.jar:/Users/chenshun/.m2/repository/org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar:/Users/chenshun/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.14.1/log4j-to-slf4j-2.14.1.jar:/Users/chenshun/.m2/repository/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar:/Users/chenshun/.m2/repository/org/slf4j/jul-to-slf4j/1.7.32/jul-to-slf4j-1.7.32.jar:/Users/chenshun/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/chenshun/.m2/repository/org/springframework/spring-core/5.3.9/spring-core-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-jcl/5.3.9/spring-jcl-5.3.9.jar:/Users/chenshun/.m2/repository/org/yaml/snakeyaml/1.28/snakeyaml-1.28.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.5.3/spring-boot-starter-json-2.5.3.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.12.4/jackson-databind-2.12.4.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.12.4/jackson-annotations-2.12.4.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.12.4/jackson-core-2.12.4.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.4/jackson-datatype-jdk8-2.12.4.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.4/jackson-datatype-jsr310-2.12.4.jar:/Users/chenshun/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.12.4/jackson-module-parameter-names-2.12.4.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.5.3/spring-boot-starter-tomcat-2.5.3.jar:/Users/chenshun/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.50/tomcat-embed-core-9.0.50.jar:/Users/chenshun/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.50/tomcat-embed-el-9.0.50.jar:/Users/chenshun/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.50/tomcat-embed-websocket-9.0.50.jar:/Users/chenshun/.m2/repository/org/springframework/spring-web/5.3.9/spring-web-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-beans/5.3.9/spring-beans-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-webmvc/5.3.9/spring-webmvc-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-aop/5.3.9/spring-aop-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-context/5.3.9/spring-context-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/spring-expression/5.3.9/spring-expression-5.3.9.jar:/Users/chenshun/.m2/repository/org/springframework/boot/spring-boot-loader/2.5.3/spring-boot-loader-2.5.3.jar com.example.demo.View

java -jar 启动

通过java -jar启动的方式,在内部通过自定义classloader加载了fat-jar内部的BOOT-INF中间的jar包,读取不到部分文件。

解压文件进行启动

通过file协议进行读取,可以读取到java -jar 读取不到的文件。