自定义类加载器

来源:互联网 发布:阿里云 mongodb 编辑:程序博客网 时间:2024/06/15 13:39

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 加载,而不会通过我们自定义类加载器来加载。

原创粉丝点击