目录

Java 类加载机制概述

类加载流程

验证 ClassLoader

自定义类加器

class 文件准备

NetworkClassLoader


Java 类加载机制概述

1、Java 虚拟机使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java文件)编译成 Java 字节码(.class文件),然后类加载器会读取 .class 文件,并转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 虚拟机可以利用 newInstance 之类的方法创建其真正对象了。

2、ClassLoader 是 Java 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader,它们被用来加载不同来源的 Class 文件。

public abstract class java.lang.ClassLoader extends Object { }

.class 文件来源?

1、既然类加载器(ClassLoader)读取字节码文件(.class文件),那么.class文件有哪些来源呢?

1)最常见的是开发者在应用程序中编写的类,这些类位于项目目录下,会由.java文件自动编译成.class文件;

2)Java 内部自带的核心类如 java.lang、java.math、java.io 等包内部的类,位于 $JAVA_HOME/jre/lib/ 目录下,如 java.lang.String 类就是定义在 $JAVA_HOME/jre/lib/rt.jar 文件里(jar文件就是打包编译好的.class文件集合)

3)Java 核心扩展类,位于 $JAVA_HOME/jre/lib/ext 目录下,开发者也可以把自己编写的类打包成 jar 文件放入该目录下

4)最后一种是动态加载远程的 .class 文件

不同的类加载器加载不同的 .class

1、针对上面四种来源的类文件(.class文件),分别有不同的加载器负责加载:

1)级别最高的 Java 核心类,即$JAVA_HOME/jre/lib 中的核心 jar 文件,这些类是 Java 运行的基础类,由一个名为 BootstrapClassLoader 加载器负责加载,它也被称作 根加载器/引导加载器。注意 BootstrapClassLoader 比较特殊,它不继承 ClassLoader,而是由 JVM 内部实现;

2)Java 核心扩展类,即 $JAVA_HOME/jre/lib/ext 目录下的 jar 文件,这些类由 ExtensionClassLoader 负责加载,它也被称作 扩展类加载器。用户如果把自己开发的 jar 文件放在这个目录,也会被 ExtClassLoader 加载;

3)开发者在项目中编写的类,这些类由 AppClassLoader 加载器进行加载,它也被称作 系统类加载器 

4)如果想远程加载(如本地文件/网络下载)的方式,则必须要自己自定义一个 ClassLoader,复写其中的 findClass() 方法才能得以实现。

2、因此可以总结出 Java 中提供了至少四种 ClassLoader 来分别加载不同来源的 Class。

类加载流程

1、不同的 ClassLoader 加载不同来源的类,当查找一个类时,优先BootstrapClassLoader遍历最高级别的 Java 核心类,然后ExtensionClassLoader 再去遍历 Java 核心扩展类,最后 AppClassLoader 再遍历用户自定义类,整个遍历过程是一旦找到就立即停止遍历。在 Java 中,这种实现方式也称作 双亲委托

2、可以将 BootstrapClassLoader 想象为核心高层领导人, ExtClassLoader 想象为中层干部, AppClassLoader 想象为普通公务员。每次需要加载一个类,先获取一个系统加载器 AppClassLoader 的实例(ClassLoader.getSystemClassLoader()),然后向上级层层请求,由最上级优先去加载,如果上级觉得这些类不属于核心类,就可以下放到各子级负责人去自行加载。

验证 ClassLoader

/**
 * Created by Administrator on 2018/6/21 0021.
 * 音乐播放器
 */
public class MusicPlayer {
    public static void main(String[] args) {
        /**获取类加载器对象*/
        ClassLoader classLoader = MusicPlayer.class.getClassLoader();
        /**获取类加载器名称*/
        System.out.println(classLoader.getClass().getName());//输出:sun.misc.Launcher$AppClassLoader

        ClassLoader classLoader_ext = classLoader.getParent();
        System.out.println(classLoader_ext.getClass().getName());//输出:sun.misc.Launcher$ExtClassLoader

        ClassLoader classLoader_boot = classLoader_ext.getParent();
        System.out.println(classLoader_boot);//输出:null
    }
}

1、可以发现 ExtClassLoader 确实是 AppClassLoader 的双亲,不过却没有看到 BootstrapClassLoader,因为 BootstrapClassLoader 比较特殊,它是由 JVM 内部实现的,所以 ExtClassLoader.getParent() = null。

自定义类加器

1、自定义类加载器,允许 JVM 在运行时可以从本地磁盘或网络上动态加载自定义类(.class文件),这使得开发者可以动态修复某些有问题的类,热更新代码。

2、下面来实现一个网络类加载器,这个加载器可以从网络上动态下载 .class 文件并加载到虚拟机中使用。

3、自定义类加载器流程如下:

1)自定义 NetworkClassLoader 类继承  ClassLoader

2)实现 ClassLoader 类的 findClass() 方法。注意:ClassLoader 自己提供了 loadClass(),它会基于双亲委托机制去搜索某个 class,当搜索不到时会调用自身的 findClass(),如果直接复写loadClass(),那还要实现双亲委托机制;

3)在 findClass() 方法里,要从网络上下载一个 .class 文件,然后转化成 Class 对象供虚拟机使用。

class 文件准备

class 文件除了放在网络上访问,同理也可以放在本地磁盘。

/**
 * Created by Administrator on 2018/6/21 0021.
 * 音乐播放器
 */
public class MusicPlayer {
    /**
     * 模拟播放音乐
     */
    public void player() {
        System.out.println("music is player ...");
    }

    /**
     * 音乐直播
     *
     * @param musicName:歌曲名称
     * @return
     */
    public String musicLivePlayer(String musicName) {
        return "music " + musicName + " player is success";
    }
}

NetworkClassLoader

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
/**
 * Created by Administrator on 2018/6/21 0021.
 * 自定义网络类加载器---继承ClassLoader
 */
public class NetworkClassLoader extends ClassLoader {
    /**
     * 必须重写findClass方法
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (name == null || "".equals(name.trim())) {
            return null;
        }
        /**下载网络上的Class文件,返回字节数组*/
        byte[] classData = downloadClassData(name);
        if (classData == null || classData.length <= 0) {
            super.findClass(name);
        } else {
            /**调用ClassLoader重载的defineClass方法
             * 将一个 byte 数组转换为 Class 类的实例。必须分析 Class,然后才能使用它。
             * name:所需要的类的二进制名称,如果不知道此名称,则该参数为 null
             *      classData已经包含了class信息,name设置为null也是可以的
             *      当name不为null时,必须是目标类的全路径,如:org.xyjt.filter.MusicPlayer
             */
            return defineClass(null, classData, 0, classData.length);
        }
        return null;
    }

    /**
     * 下载网络上的Class文件
     *
     * @param name :.class文件名,也是目标类的名字,如:MusicPlayer
     * @return 返回字节数组
     */
    public byte[] downloadClassData(String name) {
        try {
            /**
             * path拼接完成后如:http://localhost:8080/webProject/javaClass/MusicPlayer.class
             * 这个路径只有确保网络上能访问到此.class文件即可,为了方便立即才写死在这
             */
            String path = "http://localhost:8080/webProject/javaClass/" + name + ".class";
            URL url = new URL(path);
            InputStream inputStream = url.openStream();

            /**用一个字节数组输出流来接收
             * 边读边存,都是常用的IO操作*/
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int buffSize = 2048;
            byte[] buffer = new byte[buffSize];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
            /**最后返回字节数组*/
            return byteArrayOutputStream.toByteArray();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        try {
            /**调用的目标.class文件的名称*/
            String className = "MusicPlayer";

            /**用自定义类加载器类加载此类
             * loadClass方法会使用双亲委托,当找不到时,它默认就是调用上面的findClass方法
             * 当然也可以直接networkClassLoader.findClass("className)*/
            NetworkClassLoader networkClassLoader = new NetworkClassLoader();
            /**aClass就是网络上的动态加载的org.xyjt.filter.MusicPlayer类*/
            Class aClass = networkClassLoader.loadClass(className);

            /** 这里肯定输出的是自定义累加器而不是AppClassLoader、ExtClassLoader、BootstrapClassLoader*/
            System.out.println("1、"+aClass.getClassLoader().getClass().getName());
            System.out.println("2、"+aClass.getName());

            /**调用MusicPlayer方法*/
            Method playerMethod = aClass.getMethod("player", null);
            playerMethod.invoke(aClass.newInstance(),null);

            Method musicLivePlayerMethod = aClass.getMethod("musicLivePlayer", String.class);
            Object result = musicLivePlayerMethod.invoke(aClass.newInstance(), "千年等一回");
            System.out.println("3、"+result);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐