精华内容
下载资源
问答
  • 自定义classloader的使用
  • 自定义ClassLoader

    千次阅读 2017-03-13 17:30:29
    不知道大家有没有发现,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class...

    转载自:http://blog.csdn.net/briblue/article/details/54973413

    不知道大家有没有发现,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?

    如果要这样做的话,需要我们自定义一个classloader。

    自定义步骤

    1. 编写一个类继承自ClassLoader抽象类。
    2. 复写它的findClass()方法。
    3. findClass()方法中调用defineClass()

    defineClass()

    这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。

    注意点:

    一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

    上面说的是,如果自定义一个ClassLoader,默认的parent父加载器是AppClassLoader,因为这样就能够保证它能访问系统内置加载器加载成功的class文件。

    自定义ClassLoader示例之DiskClassLoader。

    假设我们需要一个自定义的classloader,默认加载路径为D:\lib下的jar包和资源。

    我们写编写一个测试用的类文件,Test.java

    Test.java

    package com.frank.test;
    
    public class Test {
    
        public void say(){
            System.out.println("Say Hello");
        }
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后将它编译过年class文件Test.class放到D:\lib这个路径下。

    DiskClassLoader

    我们编写DiskClassLoader的代码。

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    
    public class DiskClassLoader extends ClassLoader {
    
        private String mLibPath;
    
        public DiskClassLoader(String path) {
            // TODO Auto-generated constructor stub
            mLibPath = path;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // TODO Auto-generated method stub
    
            String fileName = getFileName(name);
    
            File file = new File(mLibPath,fileName);
    
            try {
                FileInputStream is = new FileInputStream(file);
    
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int len = 0;
                try {
                    while ((len = is.read()) != -1) {
                        bos.write(len);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                byte[] data = bos.toByteArray();
                is.close();
                bos.close();
    
                return defineClass(name,data,0,data.length);
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        //获取要加载 的class文件名
        private String getFileName(String name) {
            // TODO Auto-generated method stub
            int index = name.lastIndexOf('.');
            if(index == -1){ 
                return name+".class";
            }else{
                return name.substring(index)+".class";
            }
        }
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    我们在findClass()方法中定义了查找class的方法,然后数据通过defineClass()生成了Class对象。

    测试

    现在我们要编写测试代码。我们知道如果调用一个Test对象的say方法,它会输出”Say Hello”这条字符串。但现在是我们把Test.class放置在应用工程所有的目录之外,我们需要加载它,然后执行它的方法。具体效果如何呢?我们编写的DiskClassLoader能不能顺利完成任务呢?我们拭目以待。

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class ClassLoaderTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            //创建自定义classloader对象。
            DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
    }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们点击运行按钮,结果显示。

    这里写图片描述

    可以看到,Test类的say方法正确执行,也就是我们写的DiskClassLoader编写成功。

    回首

    讲了这么大的篇幅,自定义ClassLoader才姗姗来迟。 很多同学可能觉得前面有些啰嗦,但我按照自己的思路,我觉得还是有必要的。因为我是围绕一个关键字进行讲解的。

    关键字是什么?

    关键字 路径

    • 从开篇的环境变量
    • 到3个主要的JDK自带的类加载器
    • 到自定义的ClassLoader

    它们的关联部分就是路径,也就是要加载的class或者是资源的路径。 
    BootStrap ClassLoader、ExtClassLoader、AppClassLoader都是加载指定路径下的jar包。如果我们要突破这种限制,实现自己某些特殊的需求,我们就得自定义ClassLoader,自已指定加载的路径,可以是磁盘、内存、网络或者其它。

    所以,你说路径能不能成为它们的关键字?

    当然上面的只是我个人的看法,可能不正确,但现阶段,这样有利于自己的学习理解。

    自定义ClassLoader还能做什么?

    突破了JDK系统内置加载路径的限制之后,我们就可以编写自定义ClassLoader,然后剩下的就叫给开发者你自己了。你可以按照自己的意愿进行业务的定制,将ClassLoader玩出花样来。

    玩出花之Class解密类加载器

    常见的用法是将Class文件按照某种加密手段进行加密,然后按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定了类,并且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性。

    下面,我们编写代码。

    1.定义加密解密协议

    加密和解密的协议有很多种,具体怎么定看业务需要。在这里,为了便于演示,我简单地将加密解密定义为异或运算。当一个文件进行异或运算后,产生了加密文件,再进行一次异或后,就进行了解密。

    2.编写加密工具类

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    
    public class FileUtils {
    
        public static void test(String path){
            File file = new File(path);
            try {
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(path+"en");
                int b = 0;
                int b1 = 0;
                try {
                    while((b = fis.read()) != -1){
                        //每一个byte异或一个数字2
                        fos.write(b ^ 2);
                    }
                    fos.close();
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    我们再写测试代码

    FileUtils.test("D:\\lib\\Test.class");
     
    • 1
    • 1

    这里写图片描述 
    然后可以看见路径D:\\lib\\Test.class下Test.class生成了Test.classen文件。

    编写自定义classloader,DeClassLoader

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    
    public class DeClassLoader extends ClassLoader {
    
        private String mLibPath;
    
        public DeClassLoader(String path) {
            // TODO Auto-generated constructor stub
            mLibPath = path;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // TODO Auto-generated method stub
    
            String fileName = getFileName(name);
    
            File file = new File(mLibPath,fileName);
    
            try {
                FileInputStream is = new FileInputStream(file);
    
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int len = 0;
                byte b = 0;
                try {
                    while ((len = is.read()) != -1) {
                        //将数据异或一个数字2进行解密
                        b = (byte) (len ^ 2);
                        bos.write(b);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                byte[] data = bos.toByteArray();
                is.close();
                bos.close();
    
                return defineClass(name,data,0,data.length);
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        //获取要加载 的class文件名
        private String getFileName(String name) {
            // TODO Auto-generated method stub
            int index = name.lastIndexOf('.');
            if(index == -1){ 
                return name+".classen";
            }else{
                return name.substring(index+1)+".classen";
            }
        }
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    测试

    我们可以在ClassLoaderTest.java中的main方法中如下编码:

    DeClassLoader diskLoader = new DeClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    查看运行结果是:

    这里写图片描述

    可以看到了,同样成功了。现在,我们有两个自定义的ClassLoader:DiskClassLoader和DeClassLoader,我们可以尝试一下,看看DiskClassLoader能不能加载Test.classen文件也就是Test.class加密后的文件。

    我们首先移除D:\\lib\\Test.class文件,只剩下一下Test.classen文件,然后进行代码的测试。

    DeClassLoader diskLoader1 = new DeClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader1.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    运行结果: 
    这里写图片描述

    我们可以看到。DeClassLoader运行正常,而DiskClassLoader却找不到Test.class的类,并且它也无法加载Test.classen文件。

    Context ClassLoader 线程上下文类加载器

    前面讲到过Bootstrap ClassLoader、ExtClassLoader、AppClassLoader,现在又出来这么一个类加载器,这是为什么?

    前面三个之所以放在前面讲,是因为它们是真实存在的类,而且遵从”双亲委托“的机制。而ContextClassLoader其实只是一个概念。

    查看Thread.java源码可以发现

    public class Thread implements Runnable {
    
    /* The context ClassLoader for this thread */
       private ClassLoader contextClassLoader;
    
       public void setContextClassLoader(ClassLoader cl) {
           SecurityManager sm = System.getSecurityManager();
           if (sm != null) {
               sm.checkPermission(new RuntimePermission("setContextClassLoader"));
           }
           contextClassLoader = cl;
       }
    
       public ClassLoader getContextClassLoader() {
           if (contextClassLoader == null)
               return null;
           SecurityManager sm = System.getSecurityManager();
           if (sm != null) {
               ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                      Reflection.getCallerClass());
           }
           return contextClassLoader;
       }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。

    每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置。

    我们同样可以编写代码来加深理解。 
    现在有2个SpeakTest.class文件,一个源码是

    package com.frank.test;
    
    public class SpeakTest implements ISpeak {
    
        @Override
        public void speak() {
            // TODO Auto-generated method stub
            System.out.println("Test");
        }
    
    }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    它生成的SpeakTest.class文件放置在D:\\lib\\test目录下。 
    另外ISpeak.java代码

     package com.frank.test;
    
    public interface ISpeak {
        public void speak();
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后,我们在这里还实现了一个SpeakTest.java

    package com.frank.test;
    
    public class SpeakTest implements ISpeak {
    
        @Override
        public void speak() {
            // TODO Auto-generated method stub
            System.out.println("I\' frank");
        }
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    它生成的SpeakTest.class文件放置在D:\\lib目录下。

    然后我们还要编写另外一个ClassLoader,DiskClassLoader1.java这个ClassLoader的代码和DiskClassLoader.java代码一致,我们要在DiskClassLoader1中加载位置于D:\\lib\\test中的SpeakTest.class文件。

    测试代码:

    DiskClassLoader1 diskLoader1 = new DiskClassLoader1("D:\\lib\\test");
    Class cls1 = null;
    try {
    //加载class文件
     cls1 = diskLoader1.loadClass("com.frank.test.SpeakTest");
    System.out.println(cls1.getClassLoader().toString());
    if(cls1 != null){
        try {
            Object obj = cls1.newInstance();
            //SpeakTest1 speak = (SpeakTest1) obj;
            //speak.speak();
            Method method = cls1.getDeclaredMethod("speak",null);
            //通过反射调用Test类的speak方法
            method.invoke(obj, null);
        } catch (InstantiationException | IllegalAccessException 
                | NoSuchMethodException
                | SecurityException | 
                IllegalArgumentException | 
                InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    } catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    
    DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
    System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
    new Thread(new Runnable() {
    
        @Override
        public void run() {
            System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
    
            // TODO Auto-generated method stub
            try {
                //加载class文件
            //  Thread.currentThread().setContextClassLoader(diskLoader);
                //Class c = diskLoader.loadClass("com.frank.test.SpeakTest");
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Class c = cl.loadClass("com.frank.test.SpeakTest");
                // Class c = Class.forName("com.frank.test.SpeakTest");
                System.out.println(c.getClassLoader().toString());
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        //SpeakTest1 speak = (SpeakTest1) obj;
                        //speak.speak();
                        Method method = c.getDeclaredMethod("speak",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }).start();
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    结果如下: 
    这里写图片描述

    我们可以得到如下的信息: 
    1. DiskClassLoader1加载成功了SpeakTest.class文件并执行成功。 
    2. 子线程的ContextClassLoader是AppClassLoader。 
    3. AppClassLoader加载不了父线程当中已经加载的SpeakTest.class内容。

    我们修改一下代码,在子线程开头处加上这么一句内容。

    Thread.currentThread().setContextClassLoader(diskLoader1);
     
    • 1
    • 1

    结果如下: 
    这里写图片描述

    可以看到子线程的ContextClassLoader变成了DiskClassLoader。

    继续改动代码:

    Thread.currentThread().setContextClassLoader(diskLoader);
    
     
    • 1
    • 2
    • 1
    • 2

    结果: 
    这里写图片描述

    可以看到DiskClassLoader1和DiskClassLoader分别加载了自己路径下的SpeakTest.class文件,并且它们的类名是一样的com.frank.test.SpeakTest,但是执行结果不一样,因为它们的实际内容不一样。

    Context ClassLoader的运用时机

    其实这个我也不是很清楚,我的主业是Android,研究ClassLoader也是为了更好的研究Android。网上的答案说是适应那些Web服务框架软件如Tomcat等。主要为了加载不同的APP,因为加载器不一样,同一份class文件加载后生成的类是不相等的。如果有同学想多了解更多的细节,请自行查阅相关资料。

    展开全文
  • 需求如下: 可在前端新增、修改java代码,并可实现服务不重启的前提下进行代码的部署运行。 相当于:可实现java代码的热部署。...CustomClassLoaderParam: 自定义ClassLoader包装类(不要直接使用此类中的方法,应.

    需求如下:

    1. 可在前端新增、修改java代码,并可实现服务不重启的前提下进行代码的部署运行。
    2. 相当于:可实现java代码的热部署。

    代码如下:

    1. ClassLoaderTest: 自定义classloader测试类
    2. ClazzCache: 自定义ClassLoader缓存类
    3. CustomCompiler: 自定义编译器
    4. CustomClassLoader: 自定义ClassLoader
    5. CustomClassLoaderParam: 自定义ClassLoader包装类(不要直接使用此类中的方法,应通过使用CustomClassLoader来实现功能)
    6. TestCode: 模拟前端写的java代码
    7. TestCodeUpdate: 模拟前端写的java代码

    ClassLoaderTest.java

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @ActiveProfiles("dev")
    @SpringBootTest(classes = ApiApplication.class)
    public class ClassLoaderTest {
    
        @Test
        public void classLoaderTest() throws Exception {
            // 新增操作 - 第一次加载,应通过findClass获取
            ClazzCache testCode = CustomClassLoaderParam.findClass("TestCode", 1L);
            Object invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
            System.err.println(invoke);
            // 第二次加载,应通过缓存获取
            testCode = CustomClassLoaderParam.findClass("TestCode", 1L);
            invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
            System.err.println(invoke);
            // 新增操作 - 第一次加载,应通过findClass获取
            testCode = CustomClassLoaderParam.findClass("TestCodeUpdate", 1L);
            invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
            System.err.println(invoke);
    
            // 更新操作 - 会创建新的ClassLoader,新的ClassLoader并没有加载,则会重新加载,通过findClass获取
            ClazzCache testCodeUpdate = CustomClassLoaderParam.findClass("TestCode", 2L);
            Object invokeUpdate = testCodeUpdate.getMethod().invoke(testCodeUpdate.getObj(), new EnterpriseMessage());
            System.err.println(invokeUpdate);
            // 再次获取,由于创建了新的ClassLoader,本应通过findClass获取,但做了缓存处理,则应通过缓存获取
            testCode = CustomClassLoaderParam.findClass("TestCodeUpdate", 1L);
            invoke = testCode.getMethod().invoke(testCode.getObj(), new EnterpriseMessage());
            System.err.println(invoke);
    
        }
    
    }
    

    在这里插入图片描述

    ClazzCache.java

    import lombok.*;
    
    import java.io.Serializable;
    import java.lang.reflect.Method;
    
    /**
     * 自定义classloader缓存类信息
     */
    @Getter
    @Setter
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class ClazzCache implements Serializable {
    
        /**
         * 实例对象
         */
        private Object obj;
        /**
         * 方法
         */
        private Method method;
        /**
         * 最后一次使用时间
         */
        private Long lastUsedTime = System.currentTimeMillis();
    
    }
    

    CustomCompiler.java

    import javax.tools.*;
    import javax.tools.JavaFileObject.Kind;
    import java.io.*;
    import java.net.URI;
    import java.nio.CharBuffer;
    import java.util.*;
    
    /**
     * 自定义编译器
     */
    public class CustomCompiler {
    
        private static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    
        /**
         * 将java代码编译为class字节码并返回byte数组
         */
        public static Map<String, byte[]> compiler(String className, String sourceCode) throws Exception {
            try (
                    StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
                    MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager);
                    Writer writer = new StringWriter()
            ) {
                JavaFileObject javaFileObject = manager.makeStringSource(className, sourceCode);
                JavaCompiler.CompilationTask task = compiler.getTask(writer, manager, null, null, null, Arrays.asList(javaFileObject));
                if (task.call()) {
                    return manager.getClassBytes();
                } else {
                    throw new Exception("编译错误:" + writer.toString());
                }
            }
        }
    
        /**
         * 内存Java文件管理器
         */
        static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
            final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
            final Map<String, List<JavaFileObject>> classObjectPackageMap = new HashMap<>();
            MemoryJavaFileManager(JavaFileManager fileManager) {
                super(fileManager);
            }
            public Map<String, byte[]> getClassBytes() {
                return new HashMap(this.classBytes);
            }
            @Override
            public void flush() {
    
            }
            @Override
            public void close() {
                classBytes.clear();
            }
            @Override
            public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
                Iterable<JavaFileObject> it = super.list(location, packageName, kinds, recurse);
                if (kinds.contains(Kind.CLASS)) {
                    final List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
                    if (javaFileObjectList != null) {
                        if (it != null) {
                            for (JavaFileObject javaFileObject : it) {
                                javaFileObjectList.add(javaFileObject);
                            }
                        }
                        return javaFileObjectList;
                    } else {
                        return it;
                    }
                } else {
                    return it;
                }
            }
    
            @Override
            public String inferBinaryName(Location location, JavaFileObject file) {
                if (file instanceof MemoryInputJavaClassObject) {
                    return ((MemoryInputJavaClassObject) file).inferBinaryName();
                }
                return super.inferBinaryName(location, file);
            }
    
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
                if (kind == Kind.CLASS) {
                    return new MemoryOutputJavaClassObject(className);
                } else {
                    return super.getJavaFileForOutput(location, className, kind, sibling);
                }
            }
    
            JavaFileObject makeStringSource(String className, final String code) {
                String classPath = className.replace('.', '/') + Kind.SOURCE.extension;
                return new SimpleJavaFileObject(URI.create("string:///" + classPath), Kind.SOURCE) {
                    @Override
                    public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
                        return CharBuffer.wrap(code);
                    }
                };
            }
    
            void makeBinaryClass(String className, final byte[] bs) {
                JavaFileObject javaFileObject = new MemoryInputJavaClassObject(className, bs);
                String packageName = "";
                int pos = className.lastIndexOf('.');
                if (pos > 0) {
                    packageName = className.substring(0, pos);
                }
                List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
                if (javaFileObjectList == null) {
                    javaFileObjectList = new LinkedList<>();
                    javaFileObjectList.add(javaFileObject);
                    classObjectPackageMap.put(packageName, javaFileObjectList);
                } else {
                    javaFileObjectList.add(javaFileObject);
                }
            }
    
            class MemoryInputJavaClassObject extends SimpleJavaFileObject {
                final String className;
                final byte[] bs;
    
                MemoryInputJavaClassObject(String className, byte[] bs) {
                    super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
                    this.className = className;
                    this.bs = bs;
                }
    
                @Override
                public InputStream openInputStream() {
                    return new ByteArrayInputStream(bs);
                }
    
                public String inferBinaryName() {
                    return className;
                }
            }
    
            class MemoryOutputJavaClassObject extends SimpleJavaFileObject {
                final String className;
    
                MemoryOutputJavaClassObject(String className) {
                    super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
                    this.className = className;
                }
    
                @Override
                public OutputStream openOutputStream() {
                    return new FilterOutputStream(new ByteArrayOutputStream()) {
                        @Override
                        public void close() throws IOException {
                            out.close();
                            ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                            byte[] bs = bos.toByteArray();
                            classBytes.put(className, bs);
                            makeBinaryClass(className, bs);
                        }
                    };
                }
            }
        }
    
    }
    

    CustomClassLoader.java

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.Map;
    
    /**
     * 自定义classloader
     */
    @Slf4j
    public class CustomClassLoader extends ClassLoader {
        /**
         * 存放java文件对应的字节码,findClass之前存入,defineClass之后移除
         */
        public static Map<String, byte[]> CLASS_BYTES_TEMP = new ConcurrentHashMap();
    
        /**
         * MyClassLoader单例模式:DCL方式
         */
        private static volatile CustomClassLoader instance;
        private CustomClassLoader() { }
        public static CustomClassLoader getInstance() {
            if (null == instance) {
                synchronized (CustomClassLoader.class) {
                    if (null == instance) {
                        instance = new CustomClassLoader();
                    }
                }
            }
            return instance;
        }
    
        /**
         * 重置classloader,返回新的classLoader
         */
        public synchronized static void createNewClassLoader() {
            instance = new CustomClassLoader();
        }
    
        /**
         * 编译java文件为字节码数据,并存入缓存中,供后续使用
         */
        public void compiler(String name, byte[] byteClazz) {
            CLASS_BYTES_TEMP.put(name, byteClazz);
        }
    
        /**
         * 重写findClass,自定义ClassLoader
         */
        @Override
        public Class<?> findClass(String packageName) {
            try {
                log.info(" loadClass [{}] from findClass !", packageName);
                byte[] byteClazz;
                // 从本地缓存获取byte字节码信息
                byteClazz = CLASS_BYTES_TEMP.get(packageName);
                if (byteClazz != null && byteClazz.length > 10) {
                    return defineClass(packageName, byteClazz, 0, byteClazz.length);
                }
                return null;
            } finally {
                // 从本地缓存中移除字节码信息
                CLASS_BYTES_TEMP.remove(packageName);
            }
        }
    
    }
    

    CustomClassLoaderParam.java

    import com.fintell.dp3.biz.entity.EnterpriseMessage;
    import com.fintell.dp3.common.SpringContextHolder;
    import com.fintell.dp3.common.redis.RedisClient;
    import com.fintell.dp3.common.redis.RedisContact;
    import com.fintell.dp3.extend.VariableBuiltInExtend;
    import com.fintell.tools.report.FileUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.StringUtils;
    
    import java.io.File;
    import java.lang.reflect.Method;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 自定义classloader包装类
     * 1. 通过类名进行加锁处理,保证类仅加载一次
     * 2. 判断本地缓存中,类的时间戳缓存是否存在,若不存在则表名为新增操作
     * 3. 判断本地缓存中,类的时间戳 与 要获取类的时间戳是否一致,若不一致则说明为更新操作,需要创建新的ClassLoader并重新加载类信息
     * 4. 判断本地缓存中,是否已存在类信息,若存在,则直接返回,若不存在,则使用ClassLoader.loadClass()进行加载,并存入缓存中
     */
    @Slf4j
    public class CustomClassLoaderParam {
        private static RedisClient redisClient = SpringContextHolder.getBean(RedisClient.class);
        /**
         * 扩展类的包路径,使用VariableBuiltInExtend所在的包路径
         */
        public static final String basePackage = VariableBuiltInExtend.class.getPackage().getName() + ".";
        /**
         * 类信息缓存
         */
        private static Map<String, ClazzCache> cacheClazz = new ConcurrentHashMap<>();
        /**
         * 用于记录当前加载的类的对应时间
         * key : name
         * value : 装载类的时间戳
         */
        private static Map<String, Long> updateClazzs = new ConcurrentHashMap<>();
    
        /**
         * 重写findClass,做缓存处理
         *
         * @param className        : 类名 -> 变量名
         * @param timestampCurrent : 变量最新的时间戳
         */
        public static ClazzCache findClass(String className, Long timestampCurrent) throws Exception {
            // 通过类名加锁
            synchronized (className) {
                Long timeStampLastModify = updateClazzs.get(className);
                // 1. 如果缓存中没有,则说明为新增操作,则重新装载class
                if (timeStampLastModify == null) {
                    cacheClazz.remove(className);
                }
                // 2. 如果缓存中存在,且时间不一致,则说明为更新操作,则使用新的classloader并重新装载class文件
                if (timeStampLastModify != null && timestampCurrent != timeStampLastModify) {
                    cacheClazz.remove(className);
                    CustomClassLoader.createNewClassLoader();
                }
                // 1> 从缓存中获取
                ClazzCache cache = cacheClazz.get(className);
                if (null != cache) {
                    // 2> 若缓存存在,则更新最后使用时间,并直接返回
                    cache.setLastUsedTime(System.currentTimeMillis());
                    log.info(" loadClass [{}] from cache !", className);
                    return cache;
                }
                // 1. 若缓存中获取失败,则说明需要重新装载class文件,findClass之前先进行编译java数据为字节码数组
                CustomClassLoader.getInstance().compiler(basePackage + className, getClazzCode(className));
                // 2. 使用新的ClassLoader重新加载class
                Class aClass = CustomClassLoader.getInstance().loadClass(basePackage + className);
                // 3. 将数据放入本地缓存
                Method method = aClass.getDeclaredMethod("invoke", EnterpriseMessage.class);
                method.setAccessible(true);
                ClazzCache clazzCache = ClazzCache.builder().obj(aClass.newInstance()).method(method).build();
                cacheClazz.put(className, clazzCache);
                // 4. 更新缓存时间
                updateClazzs.put(className, timestampCurrent);
                // 5. 返回缓存类信息
                return clazzCache;
            }
        }
    
        /**
         * 获取字节码信息
         */
        private static byte[] getClazzCode(String className) throws Exception{
            byte[] byteClazz;
            // 1. 从redis获取字节码
            byteClazz = (byte[]) redisClient.getObj(RedisContact.getJavaCodeKey(className));
            // 1.1 若从redis获取到字节码信息,则加载class
            if (byteClazz != null && byteClazz.length > 10) {
                return byteClazz;
            }
            // 2. 从数据库获取源码并进行编译
            String sourceCode = getSourceCode(className);
            if (sourceCode == null || StringUtils.isEmpty(sourceCode)) {
                throw new Exception("自定义变量不存在或变量逻辑不存在!");
            }
            // 3. 对源码进行编译
            Map<String, byte[]> compiler = CustomCompiler.compiler(className, sourceCode);
            byteClazz = compiler.get(basePackage + className);
            // 4. 放入缓存
            redisClient.set(RedisContact.getJavaCodeKey(className), byteClazz);
            return byteClazz;
        }
    
        /**
         * 模拟从数据库获取源码
         */
        private static String getSourceCode(String className) throws Exception {
            return FileUtil.readFile(new File("D:\\cls\\" + className + ".java"), "utf-8");
        }
    
    }
    

    TestCode.java

    package com.fintell.dp3.extend;
    
    import com.fintell.dp3.biz.entity.EnterpriseMessage;
    
    public class TestCode {
    
        public Object invoke(EnterpriseMessage enterpriseMessage) throws Exception{
            Object defaultVal = 0;
            try {
                System.err.println(" add  ");
                return defaultVal;
            } catch (NullPointerException ne) {
                return defaultVal;
            } catch (Exception e) {
                throw e;
            }
        }
    
    }
    

    TestCodeUpdate.java

    package com.fintell.dp3.extend;
    
    import com.fintell.dp3.biz.entity.EnterpriseMessage;
    
    public class TestCodeUpdate {
    
        public Object invoke(EnterpriseMessage enterpriseMessage) throws Exception{
            Object defaultVal = 0;
            try {
                System.err.println(" update  ");
                return defaultVal;
            } catch (NullPointerException ne) {
                return defaultVal;
            } catch (Exception e) {
                throw e;
            }
        }
    
    }
    
    展开全文
  • 如何自定义ClassLoader

    2020-08-21 00:22:29
    自定义ClassLoader之前要对ClassLoader的源码进行一些了解。当ClassLoader进行加载一个类时,会调用ClassLoader的loadClass方法。代码如下: /** * Loads the class with the specified <a href="#name">...

            在自定义ClassLoader之前要对ClassLoader的源码进行一些了解。当ClassLoader进行加载一个类时,会调用ClassLoader的loadClass方法。代码如下:

        /**
         * Loads the class with the specified <a href="#name">binary name</a>.
         * This method searches for classes in the same manner as the {@link
         * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
         * machine to resolve class references.  Invoking this method is equivalent
         * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
         * false)</tt>}.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class was not found
         */
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    
        /**
         * Loads the class with the specified <a href="#name">binary name</a>.  The
         * default implementation of this method searches for classes in the
         * following order:
         *
         * <ol>
         *
         *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
         *   has already been loaded.  </p></li>
         *
         *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
         *   on the parent class loader.  If the parent is <tt>null</tt> the class
         *   loader built-in to the virtual machine is used, instead.  </p></li>
         *
         *   <li><p> Invoke the {@link #findClass(String)} method to find the
         *   class.  </p></li>
         *
         * </ol>
         *
         * <p> If the class was found using the above steps, and the
         * <tt>resolve</tt> flag is true, this method will then invoke the {@link
         * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
         *
         * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
         * #findClass(String)}, rather than this method.  </p>
         *
         * <p> Unless overridden, this method synchronizes on the result of
         * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
         * during the entire class loading process.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @param  resolve
         *         If <tt>true</tt> then resolve the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         */
        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;
            }
        }
    
        /**
         * Finds the class with the specified <a href="#name">binary name</a>.
         * This method should be overridden by class loader implementations that
         * follow the delegation model for loading classes, and will be invoked by
         * the {@link #loadClass <tt>loadClass</tt>} method after checking the
         * parent class loader for the requested class.  The default implementation
         * throws a <tt>ClassNotFoundException</tt>.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         *
         * @since  1.2
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }

    由上面代码我们可以看到,当加载一个类时,会首先从已经加载的类里面去查找这个类。如果类未加载,且如果父加载器不为空,则调用父加载器的loadClass方法进行加载,如果父加载器为空,则调用BootStrap class loader加载。如果依然没有加载到类,则调用findClass方法。而findClass方法是需要子类重写的。所以我们只需要继承classLoader重写findClass方法就可以实现自定义ClassLoader。

    自定义ClassLoader实现代码:

    package com.qingcheng.classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class CustomerClassLoader extends ClassLoader{
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File file = new File("C:\\Users\\qingcheng\\Desktop\\", name.replace(".", "\\").concat(".class"));
            try (FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
               byte[] buffer = new byte[1024];
               int b = 0;
                while ((b = fis.read(buffer)) != -1) {
                    bos.write(buffer,0, b);
                }
                byte[] bytes = bos.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name);
        }
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            CustomerClassLoader customerClassLoader = new CustomerClassLoader();
            Class clazz = customerClassLoader.loadClass("com.qingcheng.CustomerClass");
    
            Method method = clazz.getMethod("testMethod");
            method.invoke(clazz.newInstance(), null);
        }
    }
    

            这里需要注意的一点:我们需要调用defineClass方法将字节数组转化为class对象。

    展开全文
  • java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 ...

    类加载器

    作用

    java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。

    层次结构

    在这里插入图片描述

    BootstrapClassLoader(启动类加载器)

    负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

    ExtensionClassLoader

    ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

    AppClassLoader

    它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方jar 包通常都是由它来加载的。

    双亲委派机制

    类加载器在加载类的时候,会优化通过父加载器去寻找需要加载的类。当父加载器找不到该类时才会通过子类去加载。

       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) {
                        // 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;
            }
        }
    

    自定义类加载器

    新建MyClassLoader类并集成ClassLoader,覆写findClass和loadClass方法。

    不要轻易覆盖 loadClass 方法,loadClass会破坏双亲委派模型。可能会导致自定义加载器无法加载内置的核心类库。在使用自定义加载器时,要明确好它的父加载器是谁,将父加载器通过子类的构造器传入。如果父类加载器是 null,那就表示父加载器是「BootstrapClassLoader」。

    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * [说明]
     *
     * @author xiaoama
     * @date 2020/8/13 13:58.
     */
    public class MyClassLoader extends ClassLoader {
        private String basePath;
    
        public MyClassLoader(String basePath) {
            this.basePath = basePath;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = loadClassData(name);
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    
        @Override
        public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
            Class<?> loadedClass = this.findLoadedClass(name);
            if (loadedClass != null) {
                return loadedClass;
            }
            else {
                try {
                    Class<?> aClass = this.findClass(name);
                    if (resolve) {
                        this.resolveClass(aClass);
                    }
                    return aClass;
                } catch (Exception var5) {
                    return super.loadClass(name, resolve);
                }
            }
        }
    
    
        public byte[] loadClassData(String className) throws IOException {
            className = className.replaceAll("\\.", "/");
            String path = basePath + File.separator + className + ".class";
            FileInputStream fis = null;
            byte[] classBytes = new byte[0];
            fis = new FileInputStream(path);
            int length = fis.available();
            classBytes = new byte[length];
            fis.read(classBytes);
            fis.close();
            return classBytes;
        }
    }
    
    

    User实体类

    public class User {
    	
    	private String name;
    
    	public String getName(){
    		return name;
    	}
    }
    
    

    写一个测试类测试自定义classloader的效果

    	public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    		MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\86180\\Desktop\\XiaoamaClassLoader\\src");
    		Class<?> aClass = myClassLoader.loadClass("User");
    		Object o = aClass.newInstance();
    		System.out.println(o);
    		System.out.println(o.getClass().getClassLoader());
    	}
    

    输出结果

    User@677327b6
    xiaoama.classloader.MyClassLoader@74a14482

    可以看到,通过我们自定义的classloader成功的加载了User类。除此之外,自定义classloader也常常用来加载需要加密的类。

    展开全文
  • 需求 可在前端新增、修改java代码,并可实现服务不重启的前提下进行代码的部署运行。...MyClassLoader:自定义classloader(不要直接使用此类中的方法,应通过使用MyClassLoaderHelp来实现功能) MyClassLoaderTe.
  • 什么是ClassLoader 当前环境JDK1.8、eclipse ClassLoader简称类加载器,主要用于加载和校验编译后的Java文件(即:以.class结尾的文件); 有哪些类加载器(ClassLoader) AppClassLoader(应用类加载器) ...
  • ClassLoader功能 类的加载 JVM运行的时候,当用到一个类的时候,需要把类字节码文件加载到内存并生成运行时数据结构,其中classLoader的角色必不可少 classLoader加载一个类的时候,会调用其如下方法 public Class&...
  • 自定义ClassLoader及其使用

    千次阅读 2018-11-24 17:48:23
    public class MyClassLoader extends ClassLoader { private String name; private String path = "d:/"; private String fileType = ".class"; public String getPath() { return path; } public void ...
  • 自定义classloader实现JAVA热替换

    万次阅读 热门讨论 2017-10-06 15:37:29
    通过思考后,于是我也自己写了一个小的WEB程序,通过java动态编译和自定义classloader也实现了一简易版本的在线JAVA编译小网页。项目地址为: https://gitee.com/puhaiyang/onlineJavaIde 预览图片: ...
  • 文章目录1、Java虚拟机的类加载机制概述2、Java虚拟机中的类加载器2.1、查看类加载器加载的路径2.1.1、查看启动类加载器2.1.2...ClassLoader4、双亲委派模式4.1、重要方法4.1.1、loadClass()4.1.2、findClass()4.1.3...
  • ClassLoader学习之 自定义classLoader和双亲委派原理1、ClassLoader原理介绍​ ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),...
  • 需要自定义classloader。 ClassLoader:加载各种class文件到JVM中。ClassLoader是抽象类。 类的加载过程分为加载阶段、连接阶段、初始化。 加载阶段:寻找class文件。 连接阶段:验证class...
  • 热修复和插件化是目前比较热门的技术,要想更好的掌握它们需要了解ClassLoader,下面这篇文章主要给大家介绍了关于Android中自定义ClassLoader耗时问题追查的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧
  • 一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职: Bootstrap ClassLoader 负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、...
  • 自定义ClassLoader对Class加密并解密

    千次阅读 2017-08-30 23:23:17
    自定义ClassLoader部分 我们将MyClassLoader修改如下 package com.dao.chu.myloader; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io....
  • 自定义 ClassLoader 加载任何类时的类名。 ":myCommand" 命令位于默认 REPL 命令之上。 scala > val hello = " hello " MyClassLoader loads classOf < root>.$line3 <<中略>> MyClassLoader loads classOf ...
  • 自定义类加载器(Classloader)是很常见的,它可以让我们从自定义的文件系统目录,网络甚至是数据库的各种文件类型(jar, war, zip等)中加载class文件。 我们项目中使用了一个开源的类管理工具PF4J,来加载指定目录下的...
  • 自定义classloader实现JAVA热替换 他的demo有二个问题。 他只是demo用反射好使。 我们不可能要求所有开发都知道反射的使用,pass 相同类进行转换出现ClassCastException异常 看看这篇: springboot环境下...
  • BootStrapClassLoader是jvm自带的一个类加载器,负责加载java核心包。 ExtClassLoader:负责加载扩展包的...也可以自定义类加载器。 热部署实现原理就是自定义类加载器。 java中的类加载器的选择是从底层往下加载的...
  • 1.有这样一个变态需求,有两个不同版本的dubbo.jar包,...package classloader; public class Dubbo { public void invoke() { System.err.println("我是Dubbo的V1版本"); } } 将上面代码export成dubbo-v1...
  • 自定义ClassLoader 热加载类 类加载过程 热加载简单来说就是在程序运行时可以重新加载之前经过编译转换后的类,Java并不支持热加载,因为我们编写的代码文件,也就是.java文件在加载前首先被Java编译器编译成....
  • 四、自定义ClassLoader

    2019-08-05 21:30:53
    参考:https://blog.csdn.net/zmx729618/article/details/78606379 JDK中的ClassLoader: protected synchronized Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException ...
  • 继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下面给出一个小demo首先定义一个类,比如MyTest,并且将其编译成class文件,然后放到一...
  • java应用环境中不同的class分别由不同的ClassLoader负责加载。 一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职: Bootstrap ClassLoader  ...
  • ClassLoader cl = new ClassLoader(urls); LogUtil.log(3,cl); LogUtil.log(4,cl.getURLs(),Arrays.toString(cl.getURLs())); Class<?> cls = cl.loadClass("demo.Demo1"); LogUtil.log(5,cls); Object ...
  • 其中,在加载阶段,虚拟机需要完成以下三件事情(也就是自定义ClassLoader需要完成的内容): 通过一个类的全限定名(包名+类名)来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构...
  • 创建自定义ClassLoader,绕过双亲委派

    千次阅读 2018-03-21 16:03:24
    ClassLoader { private String mLibPath; public MyClassLoader (String path) { mLibPath = path; } /** * 双亲委派逻辑,父加载器读不到Class才会调用此方法 */ @Override protected ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,348
精华内容 23,339
关键字:

自定义classloader