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