@TOC
背景
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
在做项目中的单元测试的时候,发现我读取不到resource下面提前设定的json资源,这个资源我是从上下文Thread.currentThread().getContextClassLoader()中读取的,debug后发现,上下文中缺少
分析一
Thread线程API中,getContextClassLoader()方法的描述为:
- Returns the context ClassLoader for this Thread. The context
- ClassLoader is provided by the creator of the thread for use
- by code running in this thread when loading classes and resources.
- If not {@linkplain #setContextClassLoader set}, the default is the
- ClassLoader context of the parent Thread. The context ClassLoader of the
- primordial thread is typically set to the class loader used to load the
- application.
If a security manager is present, and the invoker's class loader is not
- {@code null} and is not the same as or an ancestor of the context class
- loader, then this method invokes the security manager’s {@link
- SecurityManager#checkPermission(java.security.Permission) checkPermission}
- method with a {@link RuntimePermission RuntimePermission}{@code
- (“getClassLoader”)} permission to verify that retrieval of the context
- class loader is permitted.
翻译成中文大概意思为:
返回该线程的ClassLoader上下文。线程创建者提供ClassLoader上下文,以便运行在该线程的代码在加载类和资源时使用。如果没有,则默认返回父线程的ClassLoader上下文。原始线程的上下文 ClassLoader 通常设定为用于加载应用程序的类加载器。
首先,如果有安全管理器,并且调用者的类加载器不是 null,也不同于其上下文类加载器正在被请求的线程上下文类加载器的祖先,则通过 RuntimePermission(“getClassLoader”) 权限调用该安全管理器的 checkPermission 方法,查看是否可以获取上下文 ClassLoader。
下面的例子说明子线程的ClassLoader与父线程的一致:
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getId()+"-outer:"+Thread.currentThread().getContextClassLoader());
new Thread(){
public void run(){
System.out.println(Thread.currentThread().getId()+"-inner:"+Thread.currentThread().getContextClassLoader());
}
}.start();
new Thread(){
public void run(){
System.out.println(Thread.currentThread().getId()+"-inner:"+Thread.currentThread().getContextClassLoader());
}
}.start();
}
}
返回值:
1-outer:sun.misc.Launcher$AppClassLoader@18b4aac2
12-inner:sun.misc.Launcher$AppClassLoader@18b4aac2
13-inner:sun.misc.Launcher$AppClassLoader@18b4aac2
以上结果表示返回的ClassLoader是一样的。
分析二
发现上面的分析方向不对,重新来分析。
下面的代码是读取单元测试资源的代码:
public static JSONObject fileToJson(String fileName) {
JSONObject json = null;
try (
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
) {
json = JSONObject.parseObject(IOUtils.toString(is, "utf-8"));
} catch (Exception e) {
System.out.println(fileName + "文件读取异常" + e);
}
return json;
}
debug发现,Thread.currentThread().getContextClassLoader()中没有resourceAdStream对应,如果里面就没有,那么第三步获取资源流的时候就已经空了。
接下来看Thread.currentThread().getContextClassLoader()这个是怎么获取的:
java.lang中的Thread是通过如下方法获取上下文类加载器的,由代码可知,该对应是Thread的成员变量,那么该成员变量是怎么赋值的?
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
...
}
以上代码验证了分析一中的解释。
进一步debug init()方法,项目其中的所有线程都在这里初始化,具体这个resource资源应该在哪个线程中被加载进来呢?
debug后预计要查过20个线程被初始化,并没有一一点开查看。
============ 遗留 begin ===============
由于我们的项目东西比较多,Thread类有不让修改,所以,不好debug,针对该问题,我后面找一个建单的demo项目去调试下,看是哪个Thread把这个资源初始进来的。
============= 遗留 end ==============
后面通过对比发现resouce这个文件夹没有标识为resource root,将其标识为resource root后,即可读取到
结论:
1、资源是通过线程中的上下文类加载器加载进来的
2、在读取资源的时候,
src/main/java:里面的java文件只能直接加载src/main/resources下的资源,不能直接加载src/test/resources下的资源
src/test/java: 里面的java文件既能加载src/test/resources下的资源,又能加载src/main/resources下的资源,当两个resources下都有要加载的同名资源时候,优先选择src/test/java下的资源;
3、java和resource root目录需要标识