1. 关于ClassPath
在早期配置JDK时,我们常常需要配置classpath环境变量,这是jvm为了搜索jdk\lib目录下的tools.jar才配置的,现在的JDK(1.5以上)已经不需要配置了。jvm在进行类的加载时,AppClassLoader会从所有classpath目录下搜索类(在双亲委托机制下,只有当BootstrapClassPath和ExtClassLoader无法找到类时才使用AppClassLoader进行搜索),但本文主要讨论的不是这个问题,在构建项目时我们常常将配置文件等放在src目录下,打包后这些文件会在classes(web项目) / bin(java项目)
目录下,可以通过classLoader.getResource("/file.txt")
等方法获取文件,但并非所有类加载器都能得到同样的结果,根据类加载器的不同结果也会有所不同。
2. classLoader.getResource方法
在类加载器中有一个getResource方法,我们常常用这个方法去加载配置文件,当我们在一个成员方法中使用this.getClass().getClassLoader()
时,会获取当前类的类加载器,在一个简单的java项目中,一般会获得AppClassLoader,通过调用该类加载器的getResource("filename")
方法,我们可以获得放在src目录下的文件url。
1)在简单java项目中的测试
package top.actim.test;import org.junit.jupiter.api.Test;classClassPathTest{@Testvoidtest(){
ClassLoader classLoader=this.getClass().getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getResource(""));
System.out.println(classLoader.getResource("/"));
System.out.println(classLoader.getResource("file.txt"));
System.out.println(classLoader.getResource("/file.txt"));
System.out.println();
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getResource(""));
System.out.println(classLoader.getParent().getResource("/"));
System.out.println(classLoader.getParent().getResource("file.txt"));
System.out.println(classLoader.getParent().getResource("/file.txt"));
System.out.println();
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemResource(""));
System.out.println(ClassLoader.getSystemResource("/"));
System.out.println(ClassLoader.getSystemResource("file.txt"));
System.out.println(ClassLoader.getSystemResource("/file.txt"));
System.out.println();
System.out.println(Thread.currentThread().getContextClassLoader());}}
运行结果:
sun.misc.Launcher$AppClassLoader@2503dbd3
file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring->Hibernate/ClassPathTest/bin/
null
file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring->Hibernate/ClassPathTest/bin/file.txt
nullsun.misc.Launcher$ExtClassLoader@5b80350b
null
null
null
nullsun.misc.Launcher$AppClassLoader@2503dbd3
file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring->Hibernate/ClassPathTest/bin/
null
file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring->Hibernate/ClassPathTest/bin/file.txt
nullsun.misc.Launcher$AppClassLoader@2503dbd3
2)在web项目的测试
package top.actim.test;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;/**
* Servlet implementation class ClassPathTest
*/@WebServlet(value="/ClassPathTest", loadOnStartup=1)publicclassClassPathTestextendsHttpServlet{privatestaticfinallong serialVersionUID=1L;@Overridepublicvoidinit()throws ServletException{
ClassLoader classLoader=this.getClass().getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getResource(""));
System.out.println(classLoader.getResource("/"));
System.out.println(classLoader.getResource("file.txt"));
System.out.println(classLoader.getResource("/file.txt"));
System.out.println();
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getResource(""));
System.out.println(classLoader.getParent().getResource("/"));
System.out.println(classLoader.getParent().getResource("file.txt"));
System.out.println(classLoader.getParent().getResource("/file.txt"));
System.out.println();
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemResource(""));
System.out.println(ClassLoader.getSystemResource("/"));
System.out.println(ClassLoader.getSystemResource("file.txt"));
System.out.println(ClassLoader.getSystemResource("/file.txt"));
System.out.println();
System.out.println(Thread.currentThread().getContextClassLoader());}}
运行结果:
ParallelWebappClassLoader
context: ClassPathTestWeb
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@76ed5528file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/webapps/ClassPathTestWeb/WEB->INF/classes/
file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/webapps/ClassPathTestWeb/WEB-INF/classes/
file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/webapps/ClassPathTestWeb/WEB-INF/classes/file.txt
file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/webapps/ClassPathTestWeb/WEB-INF/classes/file.txtjava.net.URLClassLoader@76ed5528
file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/lib/
null
null
nullsun.misc.Launcher$AppClassLoader@5c647e05
null
null
null
nullParallelWebappClassLoader
context: ClassPathTestWeb
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@76ed5528
3)结论
对于
this.getClass().getClassLoader().getResource("")
在简单java项目中获取的是AppClassLoader
所搜索的路径也就是bin下的路径,ClassLoader.getSystemResource("")
静态方法使用的是AppClassLoader
。AppClassLoader
的父亲(Parent)加载器是ExtClassLoader
,ExtClassLoader
无法获取classpath(bin)下的文件。在web项目中
this.getClass().getClassLoader().getResource("")
可以正确加载项目classes目录下的文件,tomcat在加载部署的项目中的文件(类)时使用的是WebappClassLoader
,tomcat在加载项目时会为每一个项目创建一个WebappClassLoader
并作为其线程绑定类加载器,ParallelWebappClassLoader
这类的主要逻辑是在父类中实现的,子类只有一个方法。用于热部署时重新加载所有的类,重新加载其实只需要新建一个类加载器把类再加载一遍就可以了,这里还保留了加载器之前的状态。而ClassLoader.getSystemResource("")
静态方法(AppClassLoader
)无法正确加载项目classes下的文件。显然,getResource方法的工作机制和类加载机制是相同的,即双亲委托机制。
URLClassLoader
负责加载.../apache-tomcat-8.5.38/lib/
目录下的依赖库(javaEE)。若要获取项目类根路径(classes / bin)应使用
this.getClass().getClassLoader().getResource("")
或当前类.class.getClassLoader().getResource("")
在web项目中也可用↓,在其他项目中由线程绑定类加载器决定Thread.currentThread().getContextClassLoader().getResource("")
3. 其他相关方法
1)classLoader.getResourceAsStream
该方法可以获取对应目录下文件的字节输入流
@Testvoidtest0(){
ClassLoader classLoader=this.getClass().getClassLoader();
InputStream resourceAsStream= classLoader.getResourceAsStream("file.txt");try{
System.out.println(newBufferedReader(newInputStreamReader(resourceAsStream)).readLine());}catch(IOException e){
e.printStackTrace();}finally{try{
resourceAsStream.close();}catch(IOException e){
e.printStackTrace();}}}//输出文件内容:This is a file.
2)classLoader.getResources
@Testvoidtest1()throws IOException{
ClassLoader classLoader=this.getClass().getClassLoader();
Enumeration<URL> resources= classLoader.getResources("");while(resources.hasMoreElements())
System.out.println(resources.nextElement());}/* web项目输出结果:
*
* file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/webapps/ClassPathTestWeb/WEB-INF/classes/
* file:/E:/Program%20Files/Tomcat/apache-tomcat-8.5.38/lib/
*//* java项目输出结果
*
* file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/ClassPathTest/bin/
* file:/E:/Program%20Files/eclipse/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/configuration/org.eclipse.osgi/418/0/.cp/
* file:/E:/Program%20Files/eclipse/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/configuration/org.eclipse.osgi/416/0/.cp/
*/
假设类路径上有反复的资源,getResource()方法会返回类路径上碰到的第一个资源。
而getResources()则会返回当前类载入器路径上的全部反复资源以及父类载入器上的全部反复资源。
3)ClassLoader.getSystemClassLoader()
该方法为静态方法,获取AppClassLoader
,与之对应的其他方法也是使用AppClassLoader
进行加载。
4)Class对象的相关方法
与类加载器的区别在于,最好使用this.getClass().getResource("/")
形式。底层仍然使用其类加载器的方法,只是对参数做了一点处理,如果不以/
开头,则相对于类所在位置进行检索。
@Testvoidtest2()throws IOException{
URL resource=this.getClass().getResource("/file.txt");
URL resource0=this.getClass().getResource("file.txt");
URL resource1=this.getClass().getResource("/");
URL resource2=this.getClass().getResource("");
System.out.println(resource);
System.out.println(resource0);
System.out.println(resource1);
System.out.println(resource2);}/** 输出结果:
* file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/ClassPathTest/bin/file.txt
* null
* file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/ClassPathTest/bin/
* file:/E:/a/MyFilesNoCh/JAVA/eclipse-jee-2018-09-win32-x86_64-Spring-Hibernate/ClassPathTest/bin/top/actim/test/
**/
4. Properties文件的读取
1)Properties对象
有一组load方法,
只要根据上文所述方法获取配置文件输入流即可
@Testvoidtest3()throws IOException{
Properties p=newProperties();
p.load(this.getClass().getResourceAsStream("/test.properties"));
Set<Entry<Object, Object>> entrySet= p.entrySet();for(Entry<Object, Object> entry: entrySet){
System.out.println(entry.getKey()+" = "+ entry.getValue());}}/** 输出结果
* password = shit
* name = fuck
**/
2)ResourceBundle.getBundle(“包名.无后缀文件名”)
3)new PropertyResourceBundle(输入流)
ResourceBundle rb = new PropertyResourceBundle(输入流);
参考文章:
classloader.getresources() 介绍
类加载器–Tomcat–ParallelWebappClassLoader
关于getSystemResource, getResource 的总结
-Xbootclasspath参数、java -jar参数运行应用时classpath的设置方法
java中如何设置classpath.
为什么要配置classpath?