我是靠谱客的博主 无聊石头,最近开发中收集的这篇文章主要介绍手把手教你如何自定义一个类加载器,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

前面 认识类加载器及双亲委派模型/ 中我们认识了类加载器,本文我们来自定义一个类加载器。

为什么需要自定义类加载器?

既然JDK已经有类加载器了,为什么还需要自定义类加载器呢?大概有以下几个原因:

  • 隔离加载类

    模块隔离,将类加载到不同的应用程序中。比如Tomcat这类web应用服务器,内部定义了好几种类加载器,用于隔离web应用服务器上不同的应用程序。

  • 扩展加载源

​ 还可以从数据库、网络或其他终端上加载类

  • 防止源码泄露

​ Java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

类加载器的调用过程

我们需要从ClassLoader的源码入手来看类加载的过程。

ClassLoader中的loadClass方法源码如下:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

双亲委派的核心代码逻辑如下:

image-20210924205725008

而findClass()默认的实现是抛出ClassNotFoundException异常:

image-20210924205901750

还有一个方法我们需要注意的是defineClass(),它的作用是将字节数组转换成Class对象,源码如下:

image-20210924210505455

整个类加载的流程如下图所示:

image-20210924205135256

总结下这几个核心的方法:

loadClass:双亲委派的核心实现逻辑;

findClass:将class文件加载到内存中,是二进制的形式,最后得由defineClass来讲二进制文件转换成class文件。

自定义类加载器

所有用户自定义类加载器都应该继承ClassLoader类。
在自定义ClassLoader的子类是,我们通常有两种做法:

  • 重写loadClass方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制,不推荐)
  • 重写findClass方法 (推荐)

代码实现

package com.jvm;

import java.io.*;

//自定义类加载器
public class MyClassLoader extends ClassLoader{


    //磁盘上类的路径
    private String codePath;

    public MyClassLoader(ClassLoader parent, String codePath) {
        super(parent);
        this.codePath = codePath;
    }

    public MyClassLoader(String codePath) {
        this.codePath = codePath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        BufferedInputStream bis=null;
        ByteArrayOutputStream baos=null;

        //完整的类名
        String file = codePath+name+".class";
        try {

            //初始化输入流
            bis = new BufferedInputStream(new FileInputStream(file));
            //获取输出流
            baos=new ByteArrayOutputStream();

            int len;
            byte[] data=new byte[1024];
            while ((len=bis.read(data))!=-1){
                baos.write(data,0,len);
            }

            //获取内存中的字节数组
            byte[] bytes = baos.toByteArray();

            //调用defineClass将字节数组转换成class实例
            Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
            return clazz;

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }
}

测试

在本地D盘的目录下放置一个class文件:

image-20210924213131313

测试代码如下:

public class MyClassLoaderTest {

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("D:/");
        try {
            Class<?> clazz = myClassLoader.loadClass("AddressTest");
            //打印具体的类加载器,验证是否是由我们自己定义的类加载器加载的
            System.out.println("测试字节码是由"+clazz.getClassLoader().getClass().getName()+"加载的。。");
            Object o = clazz.newInstance();
            System.out.println(o.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

image-20210924213346405

可以看出我们使用自定义的类加载器加载了我们本地的一个class文件。

总结

双亲委派模型的实现核心方法是loadClass()、findClass()和defineClass(),其中loadClass()是核心逻辑,findClass()将文件加载到内存成为,defineClass()是将二进制文件转换为活的class文件,自定义类加载器也是从这三个核心方法入手的,我们通过重写findClass()方法来自定义了一个类加载器,成功加载了本地的一个class文件。

最后

以上就是无聊石头为你收集整理的手把手教你如何自定义一个类加载器的全部内容,希望文章能够帮你解决手把手教你如何自定义一个类加载器所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部