Java是如何加载资源文件的?(源码解毒)
上文提到应老板要求开发一个测试工具能方便的加载存于文件中的测试参数,当时考虑既然是测试,把测试参数文件和测试类放在一起岂不是很方便,但是老板说:我的需求是你把测试参数文件放到统一文件夹下比如resources目录下,当然你做的这个也可以保留。 好吧,既然老板都说了,我就开干呗,主要问题是如何在CaseloaderSupplier中获取resources文件夹的路径,很多人的第一反应是直接new File("/src/test/resources")不行吗? 当然是不行的,因为通过maven编译以后会把资源文件拷贝到target目录下边,直接通过file是定位不到的。唯一的解决方案(我目前感觉是唯一的)是使用Java提供的抽象方法:resources。可以通过Class.getResources()或者ClassLoader.getResources()。 Class.getResource底层也是将加载任务委托给ClassLoader做的。 当然,在做这个之前对resource不是太熟悉,只是偶尔会调用一下接口,所以感觉还是详细了解一下resource心里有底一点,google了很多文章,大概意思是getResource("")获取的是当前调用类路径,比如当前类路径为file:/Users/caiyao/workservice/test-caseloader/target/classes/base/ (打包以后的),getResource("/")这个获取的是classpath的根目录,比如file:/Users/caiyao/workservice/test-caseloader/target/classes/(注意:没有包名)。问题是老板要求测试参数类要在test包下的resources目录里,这样获取的是main的包下面的,那么怎样才能让在测试的时候(执行@Test方法)加载test包下的资源呢? 当然在思考这个问题之前,我还是在执行@Test时试了一下,令人迷惑的是,在执行@Test的时候拿到的已经是test包下的资源了!!!file:/Users/caiyao/workservice/test-caseloader/target/test-classes/ 如何办到的,详细看下源码:
首先看下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);
}
c1.getResource(name)这句可以看出Class是把获取资源委托给ClassLoader来执行的。进入ClassLoader的getResoure方法(java.lang.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;
}
从这里可以看出Java获取资源和加载类是同样的道理,使用的是双亲委托机制,首先执行父类加载器的getResource方法,如果父类加载器为空则执行Bootstrap的类加载器,如果父类加载的getResource没有获取到值
再从自己的上下文查找资源,该方法真正执行操作的是findResource(name)这个步骤,进入java.lang.ClassLoader#findResource,ClassLoader的默认实现直接返回null,应该看ClassLoader的实现类URLClassLoader
(从很少的源码阅读经验中我发现通过IDE断点调试能避免很多不必要的代码阅读,让注意力更集中于关注的这条线),从调试的过程可以看出,最后的结果是由AppClassLoader返回,这点很重要,后面要用到:
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
AccessController.doPrivileged方法是一个native方法,无法通过IDE进去调试,但是可以对ucp.findResource(name,true)打断点,通过调试可以看出正是该方法的执行返回了
file:/Users/caiyao/workservice/test-caseloader/target/test-classes/,要注意一点这里调用的findResource是通过该类的一个成员属性ucp中进去的,ucp持有该方法的一些上下文,断点进sun.misc.URLClassPath#findResource方法:
public URL findResource(String var1, boolean var2) {
int[] var4 = this.getLookupCache(var1);
URLClassPath.Loader var3;
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
URL var6 = var3.findResource(var1, var2);
if (var6 != null) {
return var6;
}
}
return null;
}
从该方法可以看出URL是通过this.getNextLoader(var4, var5))返回的Loader里得到的,而this正是上面URLClassLoader的成员属性ucp,回到URLClassLoader类中寻找ucp的初始化代码,可以看到一个单参数的构造方法:
public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}然后需要找到urls是重什么地方传递过来的,我以前大致了解过Java的类加载过程,知道类加载器是由AppClassLoader / ExtClassLoader / BootstrapClassLoader这样的一个层级结构组成,它们由Launcher初始化,从上面
已经得知最后的资源路径是由AppClassLoader得到,所以可以猜测AppClassLoader是URLClassLoader的子类,可能在Launcher中初始化,从Launcher中可以找到如下代码:
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
证实了猜想,AppClassLoader从java.class.path变量中获取资源路径,而java.class.path这个变量由在什么地方设置的呢? 肯定在执行java命令时设置的参数,可以 ps aux |grep Test1(Test1是我测试类的名字)查看到
当前执行的测试进程的信息,果然在执行Java命令时把如下两个路径都加到了classpath中:
/Users/caiyao/workservice/test-caseloader/target/test-classes
/Users/caiyao/workservice/test-caseloader/target/classes
而且test-classes在前面,从sun.misc.URLClassPath#findResource中的代码可以看出,在遍历classpath的时候,一旦发现了一个存在就不会再往后遍历,所以在执行test方法的时候只会拿到test目录,至于为什么在执行Test的时候自动把test的资源路径加到了classpath里,这个我没有再深入研究,猜测应该是maven做的操作,因为target这个目录就是maven的规定。
终于找到原因,可以安心的写代码了~~~
Java是如何加载资源文件的?(源码解毒)的更多相关文章
- Java加载资源文件的两种方法
处理配置文件对于Java程序员来说再常见不过了,不管是Servlet,Spring,抑或是Structs,都需要与配置文件打交道.Java将配置文件当作一种资源(resource)来处理,并且提供了两 ...
- Maven,预加载资源文件
预加载资源文件需要先启用功能: <build> <resources> <resource> <directory>src/main/resources ...
- 动态加载资源文件(ResourceDictionary)
原文:动态加载资源文件(ResourceDictionary) 在xaml中控件通过绑定静态资源StaticResource来获取样式Style有多种方式: 1.在项目的启动文件App中<App ...
- Spring boot 国际化自动加载资源文件问题
Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...
- Style样式的四种使用(包括用C#代码动态加载资源文件并设置样式)
Posted on 2012-03-23 11:21 祥叔 阅读(2886) 评论(6) 编辑 收藏 在Web开发中,我们通过CSS来控制页面元素的样式,一般常用三种方式: 1. 内联样式 ...
- PyQt5(5)——加载资源文件
在实际中我们需要美化界面,就需要许多的自定义图片. 但是我们发现直接导入图像使用,等程序运行时会报错.???? 这就需要建立资源文件并且加载它们,程序就可以顺利运行了. 设计界面是如何加载资源文件呢? ...
- ArcGIS紧凑型切片读取与应用2-webgis动态加载紧凑型切片(附源码)
1.前言 上篇主要讲了一下紧凑型切片的的解析逻辑,这一篇主要讲一下使用openlayers动态加载紧凑型切片的web地图服务. 2.代码实现 上篇已经可以通过切片的x.y.z得对应的切片图片,现在使用 ...
- Android 图片加载框架Glide4.0源码完全解析(二)
写在之前 上一篇博文写的是Android 图片加载框架Glide4.0源码完全解析(一),主要分析了Glide4.0源码中的with方法和load方法,原本打算是一起发布的,但是由于into方法复杂性 ...
- Android必学-异步加载+Android自定义View源码【申明:来源于网络】
Android必学-异步加载+Android自定义View源码[申明:来源于网络] 异步加载地址:http://download.csdn.net/detail/u013792369/8867609 ...
随机推荐
- Accoridion折叠面板
详细操作见代码: <!doctype html> <html> <head> <meta charset="UTF-8"> < ...
- 智联python 技能摘取
- pythonのsimple_tag
当我们需要在页面种直接调用py文件中的某些方法时,我们就要用到simple_tag.具体步骤如下: 1.在某个app下创建templatetags文件夹,切记该名称是不可以改变的. 2.在该文件夹下创 ...
- Basic Calculator I && II && III
Basic Calculator I Implement a basic calculator to evaluate a simple expression string. The expressi ...
- Linux Django项目测试
步骤 django项目: 依赖包 [root@web01 ~]# yum install openssl-devel bzip2-devel expat-devel gdbm-devel readli ...
- python套接字解决tcp粘包问题
python套接字解决tcp粘包问题 目录 什么是粘包 演示粘包现象 解决粘包 实际应用 什么是粘包 首先只有tcp有粘包现象,udp没有粘包 socket收发消息的原理 发送端可以是一K一K地发送数 ...
- linux 安装telnet命令及使用
一.CentOS下查看系统是否已安装telnetrpm -qa | grep telnettelnet-0.17-48.el6.x86_64telnet-server-0.17-48.el6.x86_ ...
- linux环境部署python3+django
1. 确定Linux安装C/C++编译器,在线安装: yum install gcc gcc-c++ autoconf automake 2. 安装依赖环境: yum -y install zlib- ...
- this:当前调用的对象
- PHP游戏概率方法
<?php function createRandomKey($randArr, $rateKey){ $total = 0; $chooseArr = array(); $pow = 0; / ...