Classloader与Thread.currentThread().getContextClassLoader()区别

匆匆九月,回到目前的创业公司工作已满二个多月了。回来后每天都有干不完的任务,对于每天的工作每天也很少有时间来进行总结与思考。仿佛让我想到了两年前刚来这家公司时的样子,虽然一直很忙有事干,但有一种瞎忙的感觉,每天看似很充实,但回过头来看其实是很空的,很有一种打酱油的感觉,这样长此以往下去个人感觉对于自己的职业生涯并不是什么好事。时间真的好快,明天又是国庆了,这个月还没有记录过任何文章,今天紧紧抓住2018年9月的尾巴赶紧写一篇吧!

问题描述

这周工作上遇到一个需要在JAVA项目里调用其他语言代码的需求,为了代码管理方便,我将其他语言的代码放在了项目的resource目录下,并在IDEA调试时成功通过了,直接用的ClassLoader.getSystemResource方法来实现的,待整体项目开发完毕后,将整个项目打为了fat jar,在jar里运行确报错了,报的找不到文件的异常。看到这个异常,很快锁定了报错的原因,尽管很简单,但感觉还是很有必要再次记录一下。其报错原因和去年记录的一篇文章是一样的情况(java加载jar包下的资源文件过程及原理分析https://blog.****.net/puhaiyang/article/details/77409203

为了确认,临时写了下如代码以验证

@RestController
@RequestMapping(value = "getResource")
public class GetResourceController {

    @GetMapping(value = "get")
    public ResponseEntity<String> getResource() {
        String resourcePath = "lisp/helloworld.lisp";
        URL systemResource = ClassLoader.getSystemResource(resourcePath);
        System.out.println(">>>" + systemResource);
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(">>>>>" + systemClassLoader.toString());
        System.out.println(">>>" + systemClassLoader.getResource(resourcePath));
        ClassLoader cuurentThreadClassLoader = Thread.currentThread().getContextClassLoader();
        System.out.println(">>>>>" + cuurentThreadClassLoader.toString());
        System.out.println(">>>" + cuurentThreadClassLoader.getResource(resourcePath));
        ClassLoader parentClassLoader = cuurentThreadClassLoader.getParent();
        System.out.println("parent:" + parentClassLoader);
        ClassLoader thisClassLoader = GetResourceController.class.getClassLoader();
        System.out.println("this:" + thisClassLoader);
        return new ResponseEntity<String>("ok", HttpStatus.OK);
    }


}

运行后日志如下:

Classloader与Thread.currentThread().getContextClassLoader()区别

即:

>>>file:/E:/idea_space/spring_hello/test-pro/target/classes/lisp/helloworld.lisp
>>>>>[email protected]
>>>file:/E:/idea_space/spring_hello/test-pro/target/classes/lisp/helloworld.lisp
>>>>>TomcatEmbeddedWebappClassLoader
  context: ROOT
  delegate: true
----------> Parent Classloader:
[email protected]

>>>file:/E:/idea_space/spring_hello/test-pro/target/classes/lisp/helloworld.lisp
parent:[email protected]
this:[email protected]

而通过fat jar运行结果如下:

Classloader与Thread.currentThread().getContextClassLoader()区别

那么是什么原因导致在springboot的fat jar里运行时通过Classloader就拿不到项目中的资源文件呢?通过打印的日志可以看出,是由于classloader的不同导致的,在IDEA开发工具中运行时直接调用classloader调用的是AppClassLoader,在IDEA环境时可以加载到target目录下的所有文件。而如果在springBoot项目的fat jar方式运行时仍然还是用AppClassLoader去加载项目内的静态资源的话就会找不到的,因为springBoot加载静态资源文件是用的内置tomcat的classloader去加载的,即上图中的LaunchedURLClassLoader,所以在fat jar加载时就需要用这个classLoader去加载项目中的资源文件了。

总结:

为了避免在项目中加载不到本项目中静态资源文件的BUG发生,调用静态资源的classLoader最好用Thread.currentThread().getContextClassLoader()方法来获取,因为一般同一个项目中java代码和其静态资源文件都是同一个classLoader来加载的,以此确保通过此classLoader也能加载到本项目中的资源文件。

国庆快乐!