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()无法满足的需求。如果需要一些特定的装载类型的方法,比如从网络上下载,从数据库中取出,从加密文件是提取,甚至动态地创建它们,这时就需要一个类装载器。