谈谈Java虚拟机3——动态扩展
Java 的体系结构允许动态扩展Java程序,这个过程包括运行时决定所使用的类型,装载它们,使用它们。通过传递类型的名字到java.lang.Class的 forName()方法,或者用户自定义的类装载器的loadClass()方法,可以动态扩展Java程序。两种方法都可以使运行中的程序去调用在源代 码中未曾提及的,而是在程序运行中决定的类型。动态扩展的例子如支持Java的Web浏览器,它跨网络装载applet的class文件。当浏览器启动的 时候,它不知道将要从网络上装载什么class文件,当它遇到包含这些applet的网页的时候才知道每个applet所需的类和接口的名字。
动态扩展Java程序最直接的方式就是使用java.lang.Class的forName()方法,它有两种重载形式。
public static Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); }
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader ccl = ClassLoader.getCallerClassLoader(); if (ccl != null) { sm.checkPermission( SecurityConstants.GET_CLASSLOADER_PERMISSION); } } } return forName0(name, initialize, loader); }
forName() 的三参数形式是在1.2版中加入的,将类型的全限定名装入String类型的className参数。如果boolean类型的initialize参数 为true,类型会在forName()方法返回之前连接并初使始化;如果initialize参数为false,类型会被装载,可能会被连接但是不会被 forName()方法明确地初始化。然而,如果该类型在调用forName()之前已经被初始化了,即使将false作为第二个参数传递到 forName(),返回的类型也已经被初始化了。第三个参数为ClassLoader,传递一个用户定制的类装载器的引用给forName(),让其使 用这个类装载器来请求类型。也可以指定forName()用默认的启动类装载器来请求类型,只需传递null作为ClassLoader参数。 forName()还有一个只采用一个参数的版本,它总是使用当前的类装载器(就是装载执行forName()请求的类的类装载器),并且总是初始化该类 型。两个版本的forName()方法都返回Class实例的引用,它代表被装载的类 型。如果类型无法被装载,会抛出ClassNotFoundException异常。
class.forName(实例)
Greet.java package com.xiaoruoen.test; public class Greet { public void greet(){ System.out.println("Hello"); } }
FornameTest.java package com.xiaoruoen.test; public class FornameTest { /** * @param args */ public static void main(String[] args) { try { Class c = Class.forName("com.xiaoruoen.test.Greet"); Greet greet = (Greet)c.newInstance(); greet.greet(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
动 态扩展Java程序的另外一种方式就是使用用户自定义的类装载器的loadClass()方法。如果需要用自定义的类装载器请求类型,只需调用那个类装载 器的loadClass()方法。类ClassLoader包含两个名为loadClass()的重载方法,其形式如下:
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }
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 = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }两个loadClass()方法都接受装载类型的全限定名装入String类型的name参数。loadClass()的语义和 forName()是一样的。如果loadClass()方法已经用String类型的name参数传递的全限定名装载了类型,它会返回这个已经被装载的 类型的Class实例。否则,该方法会试图用某种用户定制的方式来装载请求的类型。如果类装载器用定制的方式成功地装载了类型。loadClass()应 该返回一个Class的实例,表示新装载的类型。否则方法将抛出ClassNotFoundException异常。
双 参数版本的loadClass()中,boolean类型的resolve参数表示是否在装载时执行该类型的连接。连接包含三个步骤:校验、准备、解析。 如果resolve参数为true,loadClass()方法会确保在方法返回某个类型的Class实例之前已经装载并连接了该类型。如果 resolve参数是false,loadClass()方法仅仅去试图装载请求的类型,而不关心类型是否被连接了。双参数版本的loadClass() 是一个过时的方法,实际上从Java1.1开始,resolve参数就没有作用了。通常,应该调用单参数版本的loadClass()时,它会试图装载类 型并返回而把连接和初始化类型的进行留给虚拟机去掌握。
loadClass实例:
import java.io.InputStream; public class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String fileName = name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is = getClass().getResourceAsStream(fileName); if( is == null){ return super.loadClass(name); } try { byte[] b = new byte[is.available()]; is.read(b); return defineClass(name,b,0,b.length); } catch (IOException e) { e.printStackTrace(); } return super.loadClass(name); } }
package com.xiaoruoen.test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ClassLoaderTest { /** * @param args * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalArgumentException */ public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { MyClassLoader loader = new MyClassLoader(); Class c = loader.loadClass("com.xiaoruoen.test.Greet"); Object obj = c.newInstance(); Method method = c.getMethod("greet"); method.invoke(obj); } }
使用forName()还是调用用户自定义的类装载器的laodClass()方法取决于用户的需要。如果没有特别的类装载器的要 求,或许应该用forName(),因为forName()是动态扩展最直接的方法。另外,如果需要请求的类型在装载时就初始化(并且连接)的等方面,则 不得不使用forName()。当loadClass()方法返回类型的时候,类型有可能没有被连接,但谳用单参数版本的forName()方法或者调用 它的三参数版本并且传递true作为initialize参灵敏的值 时,返回的类型一事实上是已经被连接、初始化过了。
初 始化时很重要的。比如JDBC驱动程序通常用forName()调用装载的。因为每一个JDBC驱动程序类的静态方法都用DriverManager注册 驱动程序,这样才能被应用程序所使用,驱动程序类必须被初始化,而不是仅仅被加载。如果一个驱动程序被装载了,但是没有初始化,那么类的静态初始化方法就 无法被执行,驱动程序就没有在DriverManager中被注册,驱动程序就无法被应用程序使用。
类装载器可以满足一些forName()无法满足的需求。如果需要一些特定的装载类型的方法,比如从网络上下载,从数据库中取出,从加密文件是提取,甚至动态地创建它们,这时就需要一个类装载器。