接口类:org.springframework.core.io.Resource
三个具有代表性的实现类:
- org.springframework.web.context.support.ServletContextResource
- org.springframework.core.io.ClassPathResource
- org.springframework.core.io.UrlResource
通过XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)和loadBeanDefinitions(XmlBeanDefinitionReader reader),我们找到ApplicationContext利用ServletContextResourcePatternResolver去解析路径(配置文件的路径)。
上面可能讲的有点绕,但却是入口之一。 我们可以跳出这个细节来看,其实Resource是通过ResourceLoader去加载(名字也能看出啦),而ApplicationContex是ResourceLoader的一个实现。所以Application有getResource()的方法,但具体实例化Resource,ApplicationContex不亲自干,而是用组合的形式交给ResourcePatternResolver去处理,这是因为不同环境寻找配置文件的形式不一样,而其中PathMatchingResourcePatternResolver和ServletContextResourcePatternResolver是ResourcePatternResolver的实现,用于不同环境中加载Resource。
ServletContextResourcePatternResolver会将路径封装成Resource对象。根据路径的特性,分别封装为ServletContextResource、ClassPathResource或UrlResource对象。
这里个路径清理的工具类StringUtils.cleanPath,参考《StringUtils知识点》
我们先看一些默认路径的知识:
package org.springframework.jc;
import org.apache.commons.logging.Log;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class ClassGetResourceTest {
public static void main(String[] args) throws IOException {
System.out.println("============================================env===================================");
Map<String, String> map = System.getenv();
for (Iterator<String> itr = map.keySet().iterator(); itr.hasNext(); ) {
String key = itr.next();
System.out.println(key + "=" + map.get(key));
}
System.out.println("============================================properties===================================");
Properties properties = System.getProperties();
for(String key:properties.stringPropertyNames()){
System.out.println(key + "=" + properties.get(key)); // user.dir=/home/cherry/git/spring-framework
}
System.out.println(ClassGetResourceTest.class.getResource("")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/org/springframework/jc/
System.out.println(ClassGetResourceTest.class.getResource("/")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
//哟哟,不同包下的类,Log.class.getResource("")是不一样的!!! 可以获取jar包里的配置文件哦,如commons-logging-1.2.jar包
System.out.println(Log.class.getResource("")); //jar:file:/home/cherry/.gradle/caches/modules-2/files-2.1/commons-logging/commons-logging/1.2/4bfc12adfe4842bf07b657f0369c4cb522955686/commons-logging-1.2.jar!/org/apache/commons/logging/
System.out.println(Log.class.getResource("/")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
File file = new File("test.txt");
file.createNewFile();
System.out.println(file.getAbsolutePath()); //使用user.dir作为根目录
System.out.println(ClassGetResourceTest.class.getName() + ".ROOT");
System.out.println(ClassGetResourceTest.class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(ClassGetResourceTest.class.getClassLoader().getResource("")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
//作用和ClassGetResourceTest.class.getResource("/")一样
System.out.println(ClassGetResourceTest.class.getClassLoader().getResource("/")); //null
//ClassLoader.getResource不可以使用"/"
//不同包下的类,结果也与ClassGetResourceTest.class.getClassLoader()一样,于是不可以获取jar包里的配置文件哦,如commons-logging-1.2.jar包
System.out.println(Log.class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(Log.class.getClassLoader().getResource("")); //file:/home/cherry/git/spring-framework/spring-core/out/test/classes/
//作用和ClassGetResourceTest.class.getResource("/")一样
System.out.println(Log.class.getClassLoader().getResource("/")); //null
//ClassLoader.getResource不可以使用"/"
}
}
从上可以得出一些结论:
- user.dir是jvm的系统属性,默认是取你执行命令时的目录,也就是说如果在/mydata使用命令 /mydata/jc-test/server.sh start,此时user.dir为/mydata,如果在/mydata/other目录执行/mydata/jc-test/server.sh start,则此时user.dir为/mydata/other
- class.getResource方法,根据是否以根目录“/”开始来决定文件目录:
class.getResource(“”)相对于当前类的目录,是个相对路径。例如一些不是公共的配置文件,仅仅这个类或这个包下使用的配置文件。 另外用jar包里的class做了实验,发现可以读到jar包里的信息。
class.getResource(“/”)则是包的根地方,如…./classes/,用于公共配置文件。
- ClassLoader.getResource方法,不可以根目录“/”开始来决定文件目录,否则为null:
ClassLoader.getResource(“”)则是包的根地方,如…./classes/
ClassLoader.getResource(“”)为null
好了,我们现在回归主题,org.springframework.core.io.ClassPathResource能够根据类或加载器(classload)来加载类路径的文件,原文:
Resource implementation for class path resources. Uses either a given ClassLoader or a given Class for loading resources.
核心逻辑:
/**
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}