上文提到应老板要求开发一个测试工具能方便的加载存于文件中的测试参数,当时考虑既然是测试,把测试参数文件和测试类放在一起岂不是很方便,但是老板说:我的需求是你把测试参数文件放到统一文件夹下比如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是如何加载资源文件的?(源码解毒)的更多相关文章

  1. Java加载资源文件的两种方法

    处理配置文件对于Java程序员来说再常见不过了,不管是Servlet,Spring,抑或是Structs,都需要与配置文件打交道.Java将配置文件当作一种资源(resource)来处理,并且提供了两 ...

  2. Maven,预加载资源文件

    预加载资源文件需要先启用功能: <build> <resources> <resource> <directory>src/main/resources ...

  3. 动态加载资源文件(ResourceDictionary)

    原文:动态加载资源文件(ResourceDictionary) 在xaml中控件通过绑定静态资源StaticResource来获取样式Style有多种方式: 1.在项目的启动文件App中<App ...

  4. Spring boot 国际化自动加载资源文件问题

    Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...

  5. Style样式的四种使用(包括用C#代码动态加载资源文件并设置样式)

    Posted on 2012-03-23 11:21 祥叔 阅读(2886) 评论(6) 编辑 收藏 在Web开发中,我们通过CSS来控制页面元素的样式,一般常用三种方式: 1.       内联样式 ...

  6. PyQt5(5)——加载资源文件

    在实际中我们需要美化界面,就需要许多的自定义图片. 但是我们发现直接导入图像使用,等程序运行时会报错.???? 这就需要建立资源文件并且加载它们,程序就可以顺利运行了. 设计界面是如何加载资源文件呢? ...

  7. ArcGIS紧凑型切片读取与应用2-webgis动态加载紧凑型切片(附源码)

    1.前言 上篇主要讲了一下紧凑型切片的的解析逻辑,这一篇主要讲一下使用openlayers动态加载紧凑型切片的web地图服务. 2.代码实现 上篇已经可以通过切片的x.y.z得对应的切片图片,现在使用 ...

  8. Android 图片加载框架Glide4.0源码完全解析(二)

    写在之前 上一篇博文写的是Android 图片加载框架Glide4.0源码完全解析(一),主要分析了Glide4.0源码中的with方法和load方法,原本打算是一起发布的,但是由于into方法复杂性 ...

  9. Android必学-异步加载+Android自定义View源码【申明:来源于网络】

    Android必学-异步加载+Android自定义View源码[申明:来源于网络] 异步加载地址:http://download.csdn.net/detail/u013792369/8867609 ...

随机推荐

  1. [再寄小读者之数学篇](2014-07-27 $H^{-1}$ 中的有界集与弱收敛极限)

    设 $H^{-1}$ 是 $H^1_0$ 的对偶空间, 定义域为 $[0,1]$. 试证: (1) $\sed{h\sin (2\pi hx);\ h>0}$ 在 $H^{-1}$ 中有界; ( ...

  2. 原生js封装cookie获取、设置及删除

    使用cookie(key,value,options) 参数key,value,options(可选) function cookie(key,value,options){ if(typeof va ...

  3. 可变有序列表list

    list是一种有序的集合,可以随时添加和删除其中的元素. 声明方法 list名=[元素1,元素2,元素3,--] >>> name=['Tom','David','Tony'] &g ...

  4. php in_array() 循环大量数组时效率特别慢问题

    in_array() 会循环数组内部元素逐个匹配,特别耗时,换成以下方式,效率大大提升

  5. UML各种图总结-精华

    UML(Unified Modeling Language)是一种统一建模语言,为面向对象开发系统的产品进行说明.可视化.和编制文档的一种标准语言.下面将对UML的九种图+包图的基本概念进行介绍以及各 ...

  6. sort algorithms

    //todo #include<iostream> void swap(int *a, int *b){int temp = *a; *a = *b; *b = temp;} ; i &l ...

  7. STM32串口空闲中断

    串口初始化 #include "usart5.h" vu16 UART5_RX_STA=0; char UART5_RX_BUF[UART5_REC_LEN]; u8 UART5_ ...

  8. SQL 概述

    SQL是用于在数据库中存储,操作和检索数据的标准语言. 本教程教你如何使用SQL:MySQL,SQL Server,MS Access,Oracle,Sybase,Informix,Postgres和 ...

  9. RedHat 6配置yum源为网易镜像(转)

    概述 由于版权的问题,RedHat6不能直接使用yum一些指令,需要配置yum源为网易镜像,但是网上谈到很多:整理一下,将有用的信息整理如下,以便于能够为其他的配置服务配置使用:需要卸载掉原理系统自带 ...

  10. matplotlib基本用法-【老鱼学matplotlib】

    本文介绍一下matplotlib的最基本用法. 这次我们要显示一个线性方程的直线. 首先要引入matplotlib库,一般是用plt这个简写的,我们就按照大多数人的惯例来进行命名: import ma ...