自定义系统类加载器
ClassLoader.getSystemClassLoader()方法详解
方法说明
返回用于委托的系统类加载器, 它是新建 ClassLoader 实例的默认的委托双亲, 通常也是启动应用的类加载器.
这个方法在运行启动期间很早的时候就被调用, 在调用时首先会创建系统加载器, 而且会将其设置为调用该线程的上下文类加载器.
默认的系统类加载器是与这个类的实现相关的一个实例.
如果系统属性 java.system.class.loader 被定义了, 这个属性的值就将做为返回的类加载器的名字. 这个类是使用默认的系统类加载器所加载的, 且必须要定义一个默认的参数为 ClassLoader 的构造方法, 所生成的类加载器就被定义为新的系统类加载器.
如果安全管理器存在, 并且调用者的类加载器是不是 null 和调用者的类加载器是不一样的, 或者系统类加载器的祖先, 则此方法调用安全管理器的 checkPermission 方法与 RuntimePermission("getClassLoader")权限验证访问到系统类加载器. 如果没有, SecurityException 将被抛出.
源码解析
- // 系统类加载器
- // @GuardedBy("ClassLoader.class")
- private static ClassLoader scl;
- // 设置系统类加载器后, 将其设置为 true
- // @GuardedBy("ClassLoader.class")
- private static boolean sclSet;
- /**
- * getSystemClassLoader 方法源码
- */
- @CallerSensitive
- public static ClassLoader getSystemClassLoader() {
- initSystemClassLoader();
- if (scl == null) {
- return null;
- }
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- checkClassLoaderPermission(scl, Reflection.getCallerClass());
- }
- return scl;
- }
- // 初始化系统类加载器
- private static synchronized void initSystemClassLoader() {
- // 如果系统类加载器没有被设置
- if (!sclSet) {
- // 如果系统类加载器已经被设置, 不合理, 抛出异常
- if (scl != null)
- throw new IllegalStateException("recursive invocation");
- // Launcher 为系统类加载器和拓展类加载器的一个包装, 代码并不开源
- sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
- if (l != null) {
- Throwable oops = null;
- // 将 Launcher 默认的系统类加载器赋给了 scl
- scl = l.getClassLoader();
- try {
- // 获取到系统类加载器, 可能是系统默认的 AppClassLOader, 有可能是用户自定义的系统类加载器
- scl = AccessController.doPrivileged(
- new SystemClassLoaderAction(scl));
- } catch (PrivilegedActionException pae) {
- // 异常处理
- oops = pae.getCause();
- if (oops instanceof InvocationTargetException) {
- oops = oops.getCause();
- }
- }
- if (oops != null) {
- if (oops instanceof Error) {
- throw (Error) oops;
- } else {
- // wrap the exception
- throw new Error(oops);
- }
- }
- }
- // 系统类加载器已经被设置
- sclSet = true;
- }
- }
- class SystemClassLoaderAction
- implements PrivilegedExceptionAction<ClassLoader> {
- private ClassLoader parent;
- SystemClassLoaderAction(ClassLoader parent) {
- this.parent = parent;
- }
- public ClassLoader run() throws Exception {
- // 获取系统属性
- String cls = System.getProperty("java.system.class.loader");
- // 如果系统属性为空, 系统类加载器就是默认的 AppClassLoader
- if (cls == null) {
- return parent;
- }
- // 如果系统属性不为空, 获取 cls 所对应的 class 的构造方法对象
- Constructor<?> ctor = Class.forName(cls, true, parent)
- .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
- // 创建实例
- ClassLoader sys = (ClassLoader) ctor.newInstance(
- new Object[] { parent });
- // 将 sys 设置为当前线程的上下文类加载器
- Thread.currentThread().setContextClassLoader(sys);
- return sys;
- }
- }
自定义系统类加载器
首先, 沿用前文中的自定义类加载器 ClassLoaderTest, 添加一个构造方法:
- public ClassLoaderTest(ClassLoader classLoader) {
- super(classLoader);
- }
PS: 为什么要添加这样一个构造方法:
在上文的代码解析中其实给出了答案, 在设置了自定义系统类加载器, 通过反射的方法获取自定义系统类加载器的 Constructor 对象时, 需要调用到该构造方法.
- Constructor<?> ctor = Class.forName(cls, true, parent)
- .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
然后, 编写一个测试类
- public class Test21 {
- public static void main(String[] args) {
- System.out.println(System.getProperty("java.system.class.loader"));
- }
- }
编译该类, 通过 java 指令设置 java.system.class.loader 的值运行该类
java -Djava.system.class.loader=classloader.ClassLoaderTest classloader.Test21
运行结果如下
线程上下文类类加载器
当前类加载器
当前类加载器就是加载当前类的类加载器, 每个类都会使用自己的类加载器 (即加载自身的类加载器) 来去加载其他类(指的是所依赖的类), 如果 Class A 引用 Class B, 那么 Class A 的类加载器就会去加载 Class B(前提是 Class B 尚未被加载).
线程上下文类加载器
线程上下文类加载器的概念
线程上下文类加载器就是当前线程的 Current ClassLoader.
JDK1.2 开始引入, 类 Thread 中的 getContextClassLoader()与 setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器. 如果没有通过 setContextClassLoader(ClassLoader cl)进行设置, 线程将继承其父线程的上下文类加载器.
Java 应用运行时的初始线程的上下文类加载器是系统类加载器, 在线程中运行的代码可以通过该类加载器来加载类与资源.
线程上下文类加载器的重要性
父类加载器可以使用当前线程 Thread.currentThread().getContextClassLoader()所指定的类加载器所加载的类.
这就改变了父类加载器不能使用子类加载器或是其他没有直接父子关系的类加载器所加载的类的情况, 即改变了双亲委托模型.
在双亲委托模型下, 类加载是由下至上的, 即下层的类加载器会委托上层进行加载. 但是对于 SPI(Service Provider Interface)来说, 有些接口是 Java 核心库所提供的, 而 Java 核心库是由启动类加载器来加载的, 而这些接口的实现却来自与不同的 jar 包(厂商提供),Java 的启动类加载器是不会加载其他来源的 jar 包, 这样传统的双亲委托模型无法满足 SPI 的需求. 通过给当前线程设置上下文类加载器, 就可以由设置的上下文类加载器来实现对于接口实现类的加载.
当高层提供了统一的接口让低层去实现, 同时又要在高层加载 (或实例化) 低层的类时, 就必须要通过线程上下文类加载来帮助高层的 ClassLoader 找到并加载该类.
线程上下文类加载器的使用
线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(customizeClassLoader);
- } finally {
- Thread.currentThread().setContextClassLoader(classLoader);
- }