文章目录
接上一篇:【java 基础】闲话 ClassLoader 和 SPI
ClassLoader再探
由上一篇我们知道,不同的 ClassLoader加载的资源范围不同。除了最常见的 java 自己实现的三个类加载器以外,其实还有很多别的加载器。比如spring、 springboot、以及早些年常用的 tomcat,都有自己的加载器。
tomcat的类加载器
tomcat为什么要自定义类加载器,主要有以下几个原因:
- 作为 web 容器,可以同时运行多个应用。而不同的应用可能使用相同的jar包,但可能版本不同,为了防止出现类冲突,所以必须要对不同的应用进行隔离,方法便是不同的应用使用不同的WebAppClassLoader 实例。
- 在war包中,类的存储结构是放在/WEB-INF/classes 和/WEB-INF/lib 中,所以类的扫描路径也会不同,需要自定义扫描。
- 对于常用的包,比如 log4j 之类,如果在每个 classLoader 中都加载一次,是对资源的浪费,所以 tomcat 还存在一个共享的 ShareClassLoader作为WebAppClassLoader 的父加载器。
spring 的类加载器
spring也有自己的加载器:OverridingClassLoader,与双亲委派不同,OverridingClassLoader是自己先尝试加载,在无法加载到的情况下才会启用其父加载器(双亲委派)。即 spring 优先使用自己定义的类。
springboot 的类加载器
我们知道springboot FAT jar 的方式将依赖包都打包到了 BOOT-INF/lib 下,这就导致 java 普通的类加载器无法加载到jar 包中的 jar 包,所以 springboot实现了LaunchedURLClassLoader类加载器来实现自定义加载。
正确获取ClassLoader
通过以上描述我们大概说明了一下类加载器的一些自定义应用。而我们实际使用中,我们怎么获取正确的 ClassLoader呢?
常用的有两种方式获取 ClassLoader
- Thread.currentThread().getContextClassLoader();
- this.getClass().getClassLoader();
获取当前线程的ContextClassLoader
首先说明一下Thread.currentThread().getContextClassLoader():
在初始化 Thread 时可以指定我们要使用的 ClassLoader,默认情况下是内置的AppClassLoader(在 jvm 启动时指定),而比如 tomcat 等在会在每个应用启动时,初始化自己的 ClassLoader,并且在子线程初始化时会继承父线程的 ClassLoader(Thread 的默认行为)。
加载当前类的ClassLoader
而 this.getClass().getClassLoader()则是获取加载此类的 ClassLoader。每个类对象都会保存加载它自身的 classLoader。还有一个ClassName.class.getClassLoader(); 区别于this.getClass().getClassLoader(),用于静态方法中。
不常用的获取 ClassLoader的方法
ClassLoader.getSystemClassLoader(); //直接获取内置的AppClassLoader
ClassLoader.getPlatformClassLoader(); //直接获取内置的ExtClassLoader
使用ClassLoader的读取资源
通过以上的内容,我们说明了 ClassLoader 的扫描范围,以及如何正确的获取自己想要的 ClassLoader,搞明白了这些内容后,就可以开始应用 ClassLoader 进行资源读取了。
ClassLoader 有几个常用的资源加载方法
- public URL getResource(String name);
可以获取到相对于classpath根路径下的资源定位信息,通过 openStream 方法可以开启流读取。如果匹配到多个值,则返回第一个。 - public Enumeration getResources(String name);
同getResource,只是会返回所有匹配到的资源。 - public InputStream getResourceAsStream(String name);
同getResource,只是会返回直接返回流。 - public URL getSystemResource(String name);
不常用,直接获取 AppClassLoader,然后调用getResource(name)方法。范围更小 - public InputStream getSystemResourceAsStream(String name);
同getSystemResource方法,只是直接返回流。
注意⚠️:name 不能以/开头。
Class 的资源加载方法
- public URL getResource(String name);
如果 name 以/开头,则行为同 ClassLoader 的 getResource(name)方法。如果不以/开头,则路径是相对于当前类所在目录。 - public InputStream getResourceAsStream(String name);
同Class.getResource方法,只是直接返回流。
ok,结束收工!