关闭

Java类加载器

标签: 类的加载器
181人阅读 评论(0) 收藏 举报
分类:
                                     **类的加载器**

1、类的加载器的功能:负责将java类编译完的.class文件加载到java虚拟机中,并生成Class类的实例,进而可以通过newInstance方法生成该类的实例对象。

2、JDK自带的类加载器:引导类加载器,扩展类加载器,系统类加载器。
其中,系统类加载器继承扩展类加载器,扩展类加载器继承引导类加载器。
系统类加载器:加载classpath中记载的类(eclipse环境下),或者是加载当前目录下的.class文件(cmd环境下)。
扩展类加载器:加载jre/lib/ext目录下的.class文件
系统类加载器:加载jdk核心库中的.class文件
可以通过以下代码得到相应的加载器类,测试类ArrayType:

    public class ArrayType {

         public static void main(String[] args) {

            // 当前类的加载器 
            ClassLoader classLoader =  ArrayType.class.getClassLoader();
            while(classLoader!=null){
                System.out.println(classLoader.toString());

                // 当前类的加载器的父类加载器 
                classLoader = classLoader.getParent();
            }
         } 
    }          

注意:该例子只能返回系统类加载器和扩展类加载器,引导类加载器返回的是null,原因是引导类加载器是加载JDK核心库的, JDK的保护机制,如果是引导类加载器,则返回null。
3、自定义类加载器
JDK自带的三类加载器已经满足大部分需求,但是这三类加载器对.class文件的位置都是有要求的,如果我们动态生成的.class 文件(通过java的动态代理或者从网络上下载)不在jdk核心库或jre的扩展库或者classpath中 的时候,jdk自带的加载器无法找到.class 文件,从而程序运行时抛出ClassNotFoundException异常。
自定义类加载器的方法:
继承ClassLoader类
覆写ClassLoader类中的findClass方法
findClass方法需要调用defineClass方法将字节码(.class文件的内容)读入jvm中。
例如如下自定义类加载器:

    import java.io.*;

    public class FileSystemClassLoader extends ClassLoader {
        private String rootDir; 

        public FileSystemClassLoader(String rootDir) { 
              this.rootDir = rootDir; 
         } 

        protected Class<?> findClass(String name) throws ClassNotFoundException { 
              byte[] classData = getClassData(name); 
              if (classData == null) { 
                  throw new ClassNotFoundException(); 
              }  else { 
                  return defineClass(name, classData, 0, classData.length); 
              } 
        } 

        private byte[] getClassData(String className) { 
              String path = classNameToPath(className); 
              try { 
                     InputStream ins = new FileInputStream(path); 
                     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
                     int bufferSize = 4096; 
                     byte[] buffer = new byte[bufferSize]; 
                     int bytesNumRead = 0; 
                     while ((bytesNumRead = ins.read(buffer)) != -1) { 
                             baos.write(buffer, 0, bytesNumRead); 
                     } 
                    return baos.toByteArray(); 
                 } catch (IOException e) { 
                     e.printStackTrace(); 
             } 
             return null; 
        } 

         private String classNameToPath(String className) { 
             return rootDir + File.separatorChar 
                             + className.replace('.', File.separatorChar) + ".class"; 
         }
     } 

注意:ClassLoader中有一个方法叫loadClass,该方法属于jdk自带的代理模式,我们在自定义自己的类加载器的时候,不要覆盖该 方法,jdk的代理模式是指 类加载器在加载类的时候,会首先将其代理给父类加载器让其加载,父类加载器会查看该类是否已 经被加载过,如果没有被加载过就加载该类,如果加载不了(不在所要求的路径中),就掉用findClass方法加载(java多态性调 用子类的findClass方法)。所以自定义加载器只需要覆写findClass方法就可以了。

4、利用自定义加载器来判断Java类是否相同。
判断两个java类是否相同,不仅仅这两个类的名字以及所在的路径相同就可以了,还必须判断加载这两个类的类加载器是否相同。 我们来做如下演示:
对于同一个类,我们采用不同的加载器来加载它,看看它加载到内存中是否一样。

(一)需要验证的类:Sample.java

     public class Sample {
        private Sample instance;
        public void setSample(Object instance) {
            this.instance = (Sample)instance;
        }
     }

(二)自定义类加载器:上述的FileSystemClassLoader.java
(三)测试类:Main.java

    import java.lang.reflect.Method;

    public class Main {

        public static void main(String[] args) {
            String bootPath = "D:\\src";
            FileSystemClassLoader fscl1 = new FileSystemClassLoader(bootPath);
            FileSystemClassLoader fscl2 = new FileSystemClassLoader(bootPath);
            String className = "Sample";
            try{
                Class<?> sample1 = fscl1.loadClass(className);
                Class<?> sample2 = fscl2.loadClass(className);
                Object obj1 = sample1.newInstance();
                Object obj2 = sample2.newInstance();
                Method setSampleMethod = sample1.getMethod("setSample",java.lang.Object.class);
                setSampleMethod.invoke(obj1, obj2);
            } catch(Exception e){
                e.printStackTrace();
            }
        }
    }

结果:java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at Main.main(Main.java:27)
Caused by: java.lang.ClassCastException: Sample cannot be cast to Sample
at Sample.setSample(Sample.java:7)
… 5 more
总结:我们采用两个不同的加载器fscl1 和fscl2来加载同一个类Sample.class,在调用Sample中的setSample方法时出现异常,原因是fscl1和fscl2将Sample加载到JVM后,产生了两个不同的Class类实例,不同的类之间是不能强制转换的,所以抛出异常,就好比同样的粮食,通过面包厂加工出来的就是面包,但通过大家的肚子加工出来的那又是什么呢,模子不一样,出来的东西就是不一样。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1755次
    • 积分:59
    • 等级:
    • 排名:千里之外
    • 原创:4篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档