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

1,114

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 13 天,点击查看活动详情

日拱一卒无有尽 功不唐捐终入海

欢迎关注公众号【架构染色】交流学习

一、背景

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

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

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

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

image.png

类加载关键逻辑图示(来自网络).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…