美文网首页
Java中自定义类加载器

Java中自定义类加载器

作者: Wannay | 来源:发表于2021-04-27 02:51 被阅读0次

    1.什么是双亲委派机制

    当一个类加载器接受到类的加载请求时,首先不是自己尝试加载,它是交给它上级,一层层递交,最后交给BootStrap类加载器,如果BootStrap加载器无法完成加载,则交给Extension(扩展)加载器去加载,如果仍旧不行,则交给App类加载器去加载。也就是说只有父类无法完成加载时才会去尝试进行加载

    那么我们重写loadClass()方法是不是就能去加载核心类库了?
    不是,JDK为核心类库还提供了一层保护机制。无论是什么类加载器加载类时最终都得执行下面这个方法,而这个方法是声明为final的,不允许被子类重写,因此最终所有类加载器的类都会执行preDefineClass()方法,在这个方法对核心类库进行了保护。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                             ProtectionDomain protectionDomain)
            throws ClassFormatError
        {
            protectionDomain = preDefineClass(name, protectionDomain);
            String source = defineClassSourceLocation(protectionDomain);
            Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
            postDefineClass(c, protectionDomain);
            return c;
        }
    

    我们怎么打破双亲委派机制?

    打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass()以及findClass()方法。

    2.三次破坏双亲委派机制

    2.0 双亲委派机制的弊端

    顶层的类加载器无法加载底层的类加载器所加载的类。但是实际情况中又可能需要进行这类的操作。比如在系统类中定义了一个接口,该接口需要在App类中去实现,该接口还绑定一个工厂方法,用来创建接口的实例,而接口和工厂方法都在App类中,这时候就会出现该工厂方法无法创建App类加载器加载的App实例问题。

    2.1 第一次打破双亲委派机制

    双亲委派机制在JDK1.2才被引入,但是ClassLoader这个抽象类在Java的第一个版本中就已经存在,这个时候已经有很多用户自定义加载器了,因此在JDK的开发者无法用技术手段上去避免loadClass()方法被覆盖,因此在JDK1.2中添加了一个新的protected的方法findClass(),并引导用户尽可能去重写findClass(),而不是重写loadClass()。这样用户按照自己的意愿去加载类,又可以保证新写出来的类是符合双亲委派机制的。

    2.2 第二次打破双亲委派机制

    JDK的开发者在JDK1.3提出线程上下文加载器的概念。这个类加载器可以通过Thread类的setContextClassLoader()方法进行设置,默认为App类加载器。父类加载器可以请求子类加载器去加载某些类,已经违背了双亲委派机制,BootStrap类加载器通过线程上下文类加载器作代理去实现上层类加载器去加载下层类加载器所需要加载的类的方式,JDBC就是通过这样的方式来完成的,而需要代理的类可能有多个,在JDK1.6之后使用责任链模式来解决这个问题。

    2.3 第三次打破双亲委派机制

    第三次双亲委派机制被打破是因为用户追求程序的动态性的追求而导致的,比如代码热替换,模块热部署,希望像电脑外设一样即插即用。PHP等脚本语言就支持热插拔,当你改变某个文件时它会自动更改,无需进行Web服务器的重启。

    IBM公司主导的OSGi实现热部署的关键是它自定义的类加载器的实现,每一个模块都有自己的类加载器,当需要更换一个模块时,就把Bundle连同类加载器一起换掉。在OSGi下,类加载器的结构不再是双亲委派机制,而是一个网状机构。而Oracle在JDK1.9才引入了热插拔jigsaw。

    3.自定义类加载器

    实现我们自己的类加载器

    import java.io.BufferedInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    
    // 自定义ClassLoader有两种方式
    // 1、继承ClassLoader类,重写loadClass()方法,打破双亲委派机制,loadClass()中也调用了findClass()
    // 也就是说,重写loadClass()也得重写findClass()?
    // 2、继承ClassLoader类,重写findClass()方法,继续使用双亲委派机制,但是补充去加载某些类
    // 建议使用重写findClass(),进行小范围的改动,但是保留双亲委派机制的模型
    public class MyClassLoader extends ClassLoader {
        private final String byteCodePath;   //byteCodePath
    
        public MyClassLoader(String byteCodePath) {
            this.byteCodePath = byteCodePath;
        }
    
        public MyClassLoader(ClassLoader parent, String byteCodePath) {
            super(parent);
            this.byteCodePath = byteCodePath;
        }
    
        @Override
        protected Class<?> findClass(String className) throws ClassNotFoundException {
            String fileName = byteCodePath.concat(className).concat(".class");  //拼接.class文件的路径
            System.out.println(fileName);
            BufferedInputStream bis = null;
            ByteArrayOutputStream baos = null;
            try {
                bis = new BufferedInputStream(new FileInputStream(fileName));  //创建带缓冲的IO流
                baos = new ByteArrayOutputStream();
                int len;
                byte[] bytes = new byte[1024];
                while ((len = bis.read(bytes)) != -1) {
                    baos.write(bytes, 0, len);   //将二进制流写进baos的ByteArray中
                }
                final byte[] byteCodes = baos.toByteArray();
                return defineClass(null, byteCodes, 0, byteCodes.length);
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {  //关闭流
                try {
                    if (bis != null) {
                        bis.close();
                    }
                    if (baos != null) {
                        baos.close();
                    }
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
            return null;
        }
    }
    

    实现我们自己要加载的类

    public class Demo01 {
        public void test() {
            System.out.println("HelloWorld");
        }
    }
    

    使用javac命令编译成Demo01.class文件,放到项目外边去,不然会被AppClassLoader加载,无法看出效果。

    编写测试代码

    import java.lang.reflect.Method;
    
    public class MyClassLoaderTest {
        public static void main(String[] args) {
            //这个路径不能在当前项目下,当前项目下的所有路径都会被AppClassloader加载
            final MyClassLoader myClassLoader = new MyClassLoader("/Users/wanna/Desktop/Others/");
            try {
                // 使用反射,获取类Class对象
                final Class<?> clazz = myClassLoader.findClass("Demo01");
                System.out.println(clazz.getClassLoader());  //打印加载该类的类加载器
                final Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method m :
                        declaredMethods) {
                    m.invoke(clazz.newInstance());   //调用方法,得到HelloWorld
                    System.out.println(m.getName());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    最后输出运行结果

    /Users/wanna/Desktop/Others/Demo01.class
    com.wanna.juc.classloader.MyClassLoader@24d46ca6
    HelloWorld
    test
    

    相关文章

      网友评论

          本文标题:Java中自定义类加载器

          本文链接:https://www.haomeiwen.com/subject/kfayrltx.html