自定义类加载器

1.类的加载方法

  • 命令行启动应用时候由JVM初始化加载
  • 通过Class.forName()方法动态加载。除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
  • 通过ClassLoader.loadClass()方法动态加载。只是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

 注: Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

2.双亲委派模型

  如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

ClassLoad源码:
   protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            //检测类是否已经被加载
            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) {}
                //若父加载器和启动类加载器都无法加载类,在自己尝试加载
                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

3.自定义类加载器

首先介绍几个关键的方法
  • loadClass
    类加载方法,默认采用双亲委托模式,最好不要重写,因为容易破坏双亲委托模式。
  • findClass
    在loadClass中被调用,该类主要用于获取java类的二进制流,并调用defineClass方法进行解析并返回Class对象。这个方法是自定义类加载器重写的主要方法。
  • defineClass
    在findClass中被调用,解析字节码,将其转化为Class对象

了解了这几个方法,我们知道重写类加载器主要是重写findClass方法,下面就是一个自定义的类加载器的例子。

/**
 * Created by SJK on 2017/8/16.
 * 通过.class文件路径动态加载类
 */
public class MyClassLoader extends ClassLoader {
    /**
     *加载.class文件的方式有许多,可以从本地文件中加载,可以网络上下载,也可以从数据库中读取。同时大家也可以在加载前后进行加密解密
     */
    @Override
    protected Class<?> findClass(String path) throws ClassNotFoundException {
        //根据.class文件的路径得到类名
        String className = getClassName(path);
        //读取.class文件数据
        byte[] classData = loadClassData(path);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            //解析byte[]数据并返回Class对象
            return this.defineClass(className, classData, 0, classData.length);
        }
    }

    /**
     * 读取文件流转化为byte数组
     *
     * @param path 路径
     * @return
     */
    private byte[] loadClassData(String path) {
        try {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = is.read(buffer)) != -1) {
                bos.write(buffer, 0, length);
            }
            return bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    private String getClassName(String name) {
        if (name == null && name.trim().length() == 0) {
            return "";
        }
        //去除.class文件后缀
        if (name.indexOf(".") != -1) {
            name = name.split("\\.")[0];
        }
        //取文件名 例: xxx/xxx/a --> a
        if (name.lastIndexOf(File.separator) != -1) {
            name = name.substring(name.lastIndexOf(File.separator) + 1);
        }
        return name;
    }
}

  注意,我们不能将.class文件放在ClassPath路径下,否则由于双亲加载的原则,会直接导致该类由AppClassLoader 加载,而不会通过我们自定义类加载器来加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值