8.1.1解析和动态扩展

8.1.1解析和动态扩展

除了简单地在运行时连接类型之外,Java程序还可以在运行时决定连接哪一个类型。Java的 体系结构允许动态扩展Java程序,这个过程包括运行时决定所使用的类型,装载它们,使用它们。 通过传递类型的名字到java.lang.Class的forName ()方法,或者用户自定义的类装载器的loadClass ()方法,可以动态扩展Java程序。用户自定义的类装载器可以从java.lang.ClassLoader的任何子类创建。两种方法都可以使运行中的程序去调用在源代码中未曾提及的、 而是在程序运行中决定的类型。动态扩展的例子如支持java的Web浏览器,它跨网络装载applet 的class文件。当浏览器启动的时候,它不知道将要从网络上装载什么class文件,当它遇到包含 这些applet的网页的时候才知道每个applet所需的类和接口的名字。

动态扩展Java程序最直接的方式就是使用java.lang.Class的forName ()方法,它有两种重载 的形式:

//A method declared in class java.lang.Class:

public static Class forName ( String classMame)
throws ClassNotFoundException;

public static Class forName(String className, boolean initialize,

ClassLoader loader)

throws ClassNotFoundException;

forName ()的三参数形式是在1.2版中加人的,将类型的全限定名装入String类型的 className参数。如果boolean类型的initialize参数为true,类型会在forName ()方法返回之前连 接并初始化;如果initialize参数是false,类型会被装载,可能会被连接但是不会被forName () 方法明确地初始化。然而,如果该类型在调用forName ()之前已经被初始化了,即使将false作 为第二个参数传递到forName (),返回的类型也已经被初始化了。第三个参数为ClassLoader loader,传递一个用户定制的类装载器的引用给forName (),让其使用这个类装载器来请求类型。 也可以指定forName ()用默认的启动类装载器来请求类型,只需传递null作为ClassLoader loader参数。forName ()还有一个只采用一个参数的版本(该参数即为要装载的类型的全限定 名),它总是使用当前的类装载器(就是装载执行forName ()请求的类的类装载器),并且总是初始化该类型。两个版本的forName ()方法都返回Class实例的引用,它代表被装载的类型。 如果类型无法被装载,会抛出ClassNotFoundException异常。

动态扩展Java程序的另外一种方式就是使用用户自定义类装载器的loadClass ()方法。如果 需要用自定义的类装载器请求类型,只需调用那个类装载器的loadClass ()力法。类 ClassLoader包含两个名为loadClass ()的重载方法,其形式如下:

//A method declared in class java.lang.ClassLoader:
protected Class loadClass(String name) throws ClassNotFoundException; protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException;

两个loadClass ()方法都接受装载类型的全限定名装入String类型的name参数。loadClass () 的语义和forName ()是一样的。如果loadClass ()方法已经用String类型的name参数传递的全 限定名装载了类型,它会返回这个已经被装载的类型的Class实例。否则,该方法会试图用某种 用户定制的方式来装载请求的类型(用户定制的方式取决于用户自定义类装载器的作者)。如果 类装载器用定制的方式成功地装载了类型,loadClass ()应该返回一个Class的实例,表示新近 装载的类型。否则,方法将抛出ClassNotFoundException异常。如何编写用户自定义的类装载器 后面会详细介绍。

双参数版本的loadClass ()中,boolean类型的resolve参数表示是否在装载时执行该类型的 连接。前几章曾提到过,连接过程包含三个步骤:校验被装载的类型,准备(包括为类型分配 内存),解析类型中的符号引用,其中第三步是可选的。如果resolve参数为true,loadClass () 方法会确保在方法返回某个类型的Class实例之前已经装载并连接了该类型。如果resolve参数是 false,loadClass ()方法仅仅去试图装载请求的类型,而不关心类型是否被连接了。因为java虚 拟机的规范中对实现何时进行连接给了一定的自由,当传递false给resolve参数时,从loadClass () 方法得到的类型可能被连接了,也可能没有。双参数版本的loadClass ()是一个过时的方法, 实际上从java 1.1开始,resolve参数就没有作用了。通常,应该调用单参数版本的loadClass ()方法,它和使用resolve参数取false值的双参数版本是等价的。当调用单参数版本的loadClass () 时,它会试图装载类型并返回,而把连接和初始化类型的时机留给虚拟机去掌握。

使用forName ()还是调用用户自定义类装载器的loadClass ()方法取决于用户的需要。如 果没有特别要使用类装载器的要求,或许应该用forName (),因为forName ()是动态扩展最直 接的方法。另外,如果需要请求的类型在装载时就初始化(并且连接)的话,则不得不使用 forName ()。当loadClass ()方法返回类型的时候,类型有可能没有被连接。当调用单参数版 本的forName ()方法或者调用它的三参数版本并且传递true作为initialize参数的值时,返回的 类型一定已经被连接、初始化过了。

初始化是很重要的。比如JDBC驱动程序通常用forName ()调用装载的。因为每一个JDBC 驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序所使用, 驱动程序类必须被初始化,而不是仅仅被加载。假若一个驱动程序被装载了,但是没有初始化, 那么类的静态初始化方法就无法被执行,驱动程序就没有在DriverManager中被注册,驱动程序 就无法被应用程序使用。使用forName ()来装载驱动程序可以确保类被初始化,就可以确保 forName ()返回后应用程序就可以使用这个驱动程序了。

然而,类装载器可以满足一些forName()无法满足的需求。如果需要一些特定的装载类型 的方法,比如从网络上下载,从数据库中取出,从加密文件中提取,甚至动态地创建它们,这 时就需要一个类装载器。创建用户自定义的类装载器,其中一个重要原因就是能够以定制方式 把类型的全限定名转换成一个Java class文件格式(它定义了命名的类型)的字节数组.使用类 装载器而非forName ()的其他理由和安全性相关。第3章提到过,每一个类装载器拥有一个独 立的命名空间,这就为在不同的命名空间中装载的类型提供了一层安全防护。可以编写一个Java 程序,类型无法看见不在同一命名空间装载的其他类型。第3章还讲过,类装载器负责把装载的 代码放到保护域中,也就是说,如果安全上需要包含一种定制方式把类型装载到保护域中,就 需要使用类装载器而非forName ()。

不管是动态扩展的整体过程,还是各个类装载器使用的单独命名空间,都是解析支持的一 个方面:在虚拟机解析符号引用时,它可以选择类装载器。当解析常量池中的人口需要装载类 型的时候,虚拟机使用装载引用类型的同一个类装载器来装载所需的类型。比如,想像一个Cat 类通过常量池符号引用一个Mouse类型。假设Cat是被一个用户自定义的类装载器装载的,当虚 拟机解析指向Mouse的引用时,先检査是否Mouse已经被装载到Cat所属的命名空间中(检查装载Cat的装载器在装载Cat之前已经装载了名为Mouse的类型)。如果没有,虚拟机使用装载Cat的 同一个类装载器来请求Mouse。就算类名Mouse已经被装载到另外一个命名空间,这一点仍然成 立。使用启动类装载器装载的类型,当它的符号引用被解析时,Java虚拟机也使用启动类装载器 来装载被引用类型。使用用户自定义的类装载器装载的类型,当它的符合引用被解析时,java 虚拟机也使用同一个用户自定义的类装载器来装载被引用的类型。

原文地址:https://www.cnblogs.com/mongotea/p/11979573.html