ClassLoader:类的隐式加载和显式加载

简介: ClassLoader:类的隐式加载和显式加载

一、背景

之前蒋老师看到《当月亮守护地球 | SkyWalking Agent 守护你的应用...有它相伴才安逸》时,有对其中这行文字小有斟酌。

类加载具有传递性:如当需要加载一个类时,JVM 默认会使用(当前需要使用此类的)调用者  Class  对象的  ClassLoader  来加载此类;举例:类 A 中  import  类 B,那么类 A 的  ClassLoader  会去加载类 B。

上边这段文字看起来有点微妙,传递性好像第一次听说,这也导致了蒋老师随之表示,接下来要全面的学习ClassLoader(嗯,公众号又有高质量文章可以转载了)。

本篇是对ClassLoader中 类的隐式加载和显式加载做个直观的介绍和验证。这个知识点笔者个人认为很重要,多次帮助笔者解决日常工作中遇到的疑难杂症,如果你尚未认真研究过ClassLoader,但懵懂的认知让你觉得这个应该很简单,那请看下图,若看不懂则表明你可能并不了解ClassLoader中得一些关键逻辑;不贩卖焦虑,不感兴趣则忽略,知道个大概即可,不会它并不影响你做一个优秀的程序员。

2Zmh5D.gif

类加载关键逻辑图示(来自网络).png

二、问题与答案

1) 问题

通常我们都知道我们写的类,会被 JVM 加载,那都怎么被加载呢?

2)答案

  1. 类最常见的方式是通过import的方式引入的,那么 JVM 自动帮我们去加载。
  2. 另外一种情况是我们指定类加载器去加载,如使用 Class#forName(xxx)或者 ClassLoader#loadClass(xxx)

三、什么是自动加载

  1. 验证我们的代码中被import导入的类,在使用的时候会被当前类加载器自动加载,就是使用当前类加载器loadClass方法。
  2. 当前类加载器怎么理解, 当前类被哪个类加载器加载的,那么这个类加载器就叫做这个类的当前类加载器。那么这个类中通过import方式引入了其他类,就被 JVM 自动的使用当前类加载器的loadClass方法加载。

这个很好验证,ClassLoader 的 loadClass 方法里自己打个断点,调试一下。

四、什么是非自动加载

非自动加载就是我们指定一个类加器去加载类,根据需要从forNameloadClass中二选一。

问自己一个问题,forName是不是要遵守双亲委派模型?双亲委派的代码逻辑在 ClassLoader#loadClass 中,那是不是 forName 还会调用 ClassLoader 的 loadClass 的方法逻辑呢?答案是:会的,通过一个例子验证一下 。

1)示例准备

  • 接口定义
public interface MsgCenter {
    public boolean sendMsg(String msg);
}
复制代码
  • 接口实现:
```
public class MsgValidater {
    public boolean validate(String msg){
        return  true;
    }
}
public class MsgCenterImpl implements MsgCenter {
    @Override
    public boolean sendMsg(String msg) {
        // import MsgValidater类型
        return new MsgValidater().validate(msg);
    }
}
```
复制代码

2)编排测试方案

测试的关键逻辑如下:

  1. 先通过appClassLoader 加载接口
  2. 再通过子加载器以forName方式加载实现类
  3. 实现类实例化后并赋值给接口;调用sendMsg方法,此方法内引用了MsgValidater类。

逻辑看似复杂,只要稍微耐心一点也没什么,无非是主动加载和自动加载的组合再加上双亲委派的机制进行了一场混沌验证;笔者个人的经验是,学习这类知识最好的办法是反复调试并做好小笔记,用思维导图梳理自己的认知模型,感兴趣的话还是要调试一下,不感兴趣就别再看了。

/**
 * 把MsgCenterImpl.class 移动到子加载器的加载路径中.保证只有子加载器可加载.
 */
@Test
public void testImportLoadClass(){
    CustomClassLoader01 customClassLoader01 = new CustomClassLoader01(ClassLoader.getSystemClassLoader());
    try {
        //appClassloader 加载 接口
        MsgCenter msgCenter = null;
        //子加载器,加载 实现类
        Class<?> aClass = Class.forName("com.rock.MsgCenterImpl",false,customClassLoader01);
        //赋值 接口 = 实现类 ;这是可以的,因为子加载器可见父加载器所加载的类.
        msgCenter =(MsgCenter)aClass.newInstance();
        boolean hello = msgCenter.sendMsg("hello");
        System.out.println("after sendMsg");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}
复制代码

3)结果符合预期

CustomClassLoader start  load class :com.rock.MsgCenterImpl  ; resolve : false
CustomClassLoader findLoadedClass : null
CustomClassLoader getParent().loadClass(name) : ClassNotFoundException
CustomClassLoader loadClassData : com.rock.MsgCenterImpl
//以上信息说明:forName方法通过自定义类加载器的loadClass方法加载了com.rock.MsgCenterImpl
CustomClassLoader start  load class :com.rock.MsgCenter  ; resolve : false
CustomClassLoader findLoadedClass : null
CustomClassLoader start  load class :java.lang.Object  ; resolve : false
CustomClassLoader findLoadedClass : null
//以上信息说明:子加载器 委托父加载器加载 com.rock.MsgCenter ,java.lang.Object
CustomClassLoader start  load class :com.rock.classLoader.MsgValidater  ; resolve : false
CustomClassLoader findLoadedClass : null
CustomClassLoader getParent().loadClass(name) : ClassNotFoundException
CustomClassLoader loadClassData : com.rock.classLoader.MsgValidater
//以上信息说明:自定义加载器加载了com.rock.classLoader.MsgValidater
after sendMsg
//以上信息说明,实现类赋值给接口后,调用接口的方法执行成功.
复制代码

参考并感谢

blog.csdn.net/WeiPeng2K/a…


相关文章
|
4月前
|
前端开发 Java API
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
|
4月前
|
存储 Java 程序员
【JVM】类的声明周期(加载、连接、初始化)
【JVM】类的声明周期(加载、连接、初始化)
29 1
|
XML 存储 安全
Java源码类 - Properties类及多种读取方式
Java源码类 - Properties类及多种读取方式
165 0
对象实例化错误
对象实例化错误
95 0
|
Java Spring 容器
@Inject 标记在构造方法上的作用
@Inject 标记在构造方法上的作用
|
消息中间件 存储 安全
类是如何加载的?
类是如何加载的?
115 0
|
存储 缓存 安全
类的加载机制以及类、对象初始化的详细过程
java类的生命周期包括加载、连接(验证、准备、解析)、初始化、使用、卸载五个阶段。初始化的顺序是怎样的呢?
138 0
类的加载机制以及类、对象初始化的详细过程
|
Java Android开发
【Android 逆向】类加载器 ClassLoader ( 类加载时机 | 隐式加载 | 显示加载 | 类加载步骤 | 装载 | 链接 | 初始化 )
【Android 逆向】类加载器 ClassLoader ( 类加载时机 | 隐式加载 | 显示加载 | 类加载步骤 | 装载 | 链接 | 初始化 )
186 0
【Android 逆向】类加载器 ClassLoader ( 类加载时机 | 隐式加载 | 显示加载 | 类加载步骤 | 装载 | 链接 | 初始化 )
|
Java
【Java 虚拟机原理】Java 类中的类加载初始化细节 ( 只使用类中的常量时加载类不会执行到 ‘初始化‘ 阶段 )
【Java 虚拟机原理】Java 类中的类加载初始化细节 ( 只使用类中的常量时加载类不会执行到 ‘初始化‘ 阶段 )
415 0
【Java 虚拟机原理】Java 类中的类加载初始化细节 ( 只使用类中的常量时加载类不会执行到 ‘初始化‘ 阶段 )
|
Java
Java代码块(静态和非静态)在子父类中的执行顺序
Java代码块(静态和非静态)在子父类中的执行顺序
141 0