Java自定义类加载器

作者: 树生1995 | 来源:发表于2018-10-11 10:01 被阅读8次

    自定义类加载器的应用场景

    • 加密:如果你不想自己的代码被反编译的话。(类加密后就不能再用ClassLoader进行加载了,这时需要自定义一个类加载器先对类进行解密,再加载)。

    • 从非标准的来源加载代码:如果你的字节码存放在数据库甚至是云端,就需要自定义类加载器,从指定来源加载类。

    双亲委派

    • 我们先看一下ClassLoader类默认的loadClass方法实现
    protected synchronized Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                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
                    //子加载器进行类加载 
                    c = findClass(name);
                }
            }
            if (resolve) {
                //判断是否需要链接过程,参数传入
                resolveClass(c);
            }
            return c;
        }
    

    双亲委派模型工作过程如下:

    (1) 类加载器从已加载的类中查询该类是否已加载,如果已加载则直接返回。

    (2)如果在已加载的类中未找到该类,则委托给父类加载器去加载c = parent.loadClass(name, false),父类也会采用同样的策略查看自己加载的类中是否包含该类,如果没有则委托给父类,以此类推一直到启动类加载起。

    (3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用拓展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败则会抛出一个异常ClassNotFoundException,然后再调用当前加载器的findClass()方法进行加载。

    双亲委派的好处:

    (1)避免自己编写的类动态替换java的核心类,比如String

    (2)避免了类的重复加载,因为JVM区分不同类的方式不仅仅根据类名,相同的class文件被不同的类加载器加载产生的是两个不同的类。

    正题:自定义类加载器

    从上面的源码可以看出调用classLoader时会先根据委派模型在父类加在其中加载,如果加载失败则会加载当前加载器的findClass方法来加载,
    因此我们自定义的类加载器只需要继承ClassLoader,并覆盖findClass方法。

    • 准备一个class文件,编译后放到D盘根目录下
    public class People {
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 自定义类加载器MyClassLoader ,继承ClassLoader覆盖findClass方法(其中defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.nio.ByteBuffer;
    import java.nio.channels.Channels;
    import java.nio.channels.FileChannel;
    import java.nio.channels.WritableByteChannel;
     
    public class MyClassLoader extends ClassLoader
    {
        public MyClassLoader(){}
        
        public MyClassLoader(ClassLoader parent)
        {
            super(parent);
        }
        
        protected Class<?> findClass(String name) throws ClassNotFoundException
        {
            File file = new File("D:/People.class");
            try{
                byte[] bytes = getClassBytes(file);
                //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
                Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
                return c;
            } 
            catch (Exception e)
            {
                e.printStackTrace();
            }
            
            return super.findClass(name);
        }
        
        private byte[] getClassBytes(File file) throws Exception
        {
            // 这里要读入.class的字节,因此要使用字节流
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
            
            while (true){
                int i = fc.read(by);
                if (i == 0 || i == -1)
                break;
                by.flip();
                wbc.write(by);
                by.clear();
            }
            fis.close();
            return baos.toByteArray();
        }
    }
    
    • 在主函数中测试一下
    MyClassLoader mcl = new MyClassLoader(); 
    Class<?> clazz = Class.forName("People", true, mcl); 
    Object obj = clazz.newInstance();
    
    System.out.println(obj);
    //打印出我们的自定义类加载器
    System.out.println(obj.getClass().getClassLoader());
    

    参考链接:
    https://www.cnblogs.com/gdpuzxs/p/7044963.html
    https://blog.csdn.net/seu_calvin/article/details/52315125

    相关文章

      网友评论

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

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