Java之四:类的加载器ClassLoader

来源:互联网 发布:图片二维码识别软件 编辑:程序博客网 时间:2020/10/26 06:54

本博文主要讲诉java中类的加载器的使用

类在jvm中的形式:

    在JAVA的运行时环境中(Java runtime),每一个类都有一个以第一类(first-class)的Java对象所表示出现的代码,即java.lang.Class的实例。其表现形式可以表示为:

    java.lang.Class class Myclass.class;

    一旦一个类被载入JVM中,同一个类就不会再次载入了。在java中同一类用其完全匹配类名作为标志,即包名+类名;而在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此如果一个为P的保重,有一个名为C的类,被加载器ClassLoader的一个实例CL加载,于是C.class在JVM中的表示为(C,P,CL)。

类加载器:

    Java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器)

    引导类加载器负责加载jdk中的系统类,如java.lang.Object。这种类加载器都是用c语言实现的,属于系统底层执行动作,在java程序中没有办法获得这个类加载器。如获取java.lang.String.class.getClassLoader()得到为null。

    扩展类加载器负责加载标准扩展类,一般使用java实现,这是一个真正的java类加载器,负责加载java.ext.dirs属性下的类,一般为jre/lib/ext中的类。

    系统类加载器,加载第一个应用类的加载器,也就是执行java MainClass 时加载MainClass的加载器,这个加载器使用java实现,负责加载java.class.path中指定的类。

    类加载器之间有一定的关系(父子关系),我们可以认为扩展类加载器的父加载器是引导类加载器(当然不这样认为也是可以的,因为引导类加载器表现在java中就是一个null),不过系统类加载器的父加载器一定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器无法加载时才会自己去加载。

类加载器的方式:

    用户定义的类一般都是系统类加载器加载的,我们很少直接使用类加载器加载类,我们甚至很少自己加载类。因为类在使用时会被自动加载,我们用到某个类时该类会被自动加载,比如new A()会导致类A自动被加载,不过这种加载只发生一次。

    我们也可以使用系统类加载器手动加载类,ClassLoader提供了这个接口ClassLoader.getSystemClassLoader().loadClass("classFullName");这就很明确的指定了使用系统类加载器加载指定的类,但是如果该类能够被扩展类加载器加载,系统类加载器还是不会有机会的。

    常用的方式还有使用Class.forName加载使用的类,这种方式没有指定某个特定的ClassLoader,会使用调用类的ClassLoader。也就是说调用这个方法的类的类加载器将会用于加载这个类。比如在类A中使用Class.forName加载类B,那么加载类A的类加载器将会用于加载类B,这样两个类的类加载器是同一个。

类加载的工作流程:

    除了引导类加载器,所有的类加载器都有一个父类加载器,不仅如此,所有的类加载器也都是java.lang.ClassLoader类型。使用loadClass()方法可以从类加载器获取该类,可以通过java.lang.ClassLoader的源代码来了解该方法工作的细节,如下: 

protected synchronized Class loadClass (String name, boolean resolve) 

throws ClassNotFoundException{

// First check if the class is already loaded 

Class findLoadedClass(name); 

if (c == null) 

try 

if (parent != null) 

parent.loadClass(name, false); 

else 

findBootstrapClass0(name);

}

catch (ClassNotFoundException e) 

// If still not found, then invoke 

// findClass to find the class. 

findClass(name);

if (resolve) 

resolveClass(c); 

return c; 

类加载的Main实例:(转)

    上图显示了一个主类称为MyMainClass的应用程序。其中MyMainClass.class会被AppClassLoader加载。MyMainClass创建了两个类加载器的实例:CustomClassLoader1 和 CustomClassLoader2,他们可以从某数据源(比如网络)获取名为Target的字节码。这表示类Target的类定义不在应用程序类路径或扩展类路径。在这种情况下,如果MyMainClass想要用自定义的类加载器加载Target类,CustomClassLoader1和CustomClassLoader2会分别独立地加载并定义Target.class类。这在java中有重要的意义。如果Target类有一些静态的初始化代码,并且假设我们只希望这些代码在JVM中只执行一次,而这些代码在我们目前的步骤中会执行两次——分别被不同的CustomClassLoaders加载并执行。如果类Target被两个CustomClassLoaders加载并创建两个实例Target1和Target2,如图一显示,它们不是类型兼容的。换句话说,在JVM中无法执行以下代码: 

    Target target3 (Target) target2; 

    以上代码会抛出一个ClassCastException。这是因为JVM把他们视为分别不同的类,因为他们被不同的类加载器所定义。这种情况当我们不是使用两个不同的类加载器CustomClassLoader1 和 CustomClassLoader2,而是使用同一个类加载器CustomClassLoader的不同实例时,也会出现同样的错误。

创建自己的类加载器

    在 Java应用开发过程中,可能会需要创建应用自己的类加载器。典型的场景包括实现特定的Java字节代码查找方式、对字节代码进行加密/解密以及实现同名 Java类的隔离等。创建自己的类加载器并不是一件复杂的事情,只需要继承自java.lang.ClassLoader类并覆写对应的方法即可。 java.lang.ClassLoader中提供的方法有不少,下面介绍几个创建类加载器时需要考虑的:

    defineClass():这个方法用来完成从Java字节代码的字节数组到java.lang.Class的转换。这个方法是不能被覆写的,一般是用原生代码来实现的。

    findLoadedClass():这个方法用来根据名称查找已经加载过的Java类。一个类加载器不会重复加载同一名称的类。

    findClass():这个方法用来根据名称查找并加载Java类。

    loadClass():这个方法用来根据名称加载Java类。

    resolveClass:这个方法用来链接一个Java类。

    这里比较 容易混淆的是findClass()方法和loadClass()方法的作用。前面提到过,在Java类的链接过程中,会需要对Java类进行解析,而解析可能会导致当前Java类所引用的其它Java类被加载。在这个时候,JVM就是通过调用当前类的定义类加载器的loadClass()方法来加载其它类的。findClass()方法则是应用创建的类加载器的扩展点。应用自己的类加载器应该覆写findClass()方法来添加自定义的类加载逻辑。 loadClass()方法的默认实现会负责调用findClass()方法。

    前面提到,类加载器的代理模式默认使用的是父类优先的策略。这个策略的实现是封装在loadClass()方法中的。如果希望修改此策略,就需要覆写loadClass()方法。

    下面的代码给出了自定义的类加载的常见实现模式: 

public class MyClassLoader extends ClassLoader   

   protected ClassfindClass(String name) throws ClassNotFoundException       

      byte[] null; //查找或生成Java类的字节代码       

      return defineClass(name, b, 0, b.length);   

   }

}

由于博主知识有限,如有误,请指正点评,欢迎交流

0 0