实现JAVA的动态类载入机制

  作为充分利用Java的动态类载入机制的最好例子,带有Java扩展的Web浏览器根据请求从网络或本地文件系统中动态加载Java applet(遵循一定规则的Java小应用程序类),然后在本地系统中执行它,大大增强了主页的功能。
  ----其实,Java本身就是一种极具动态性的语言。类似Windows的动态链接库(DLL),Java应用程序总是被编译成若干个单独的class文件,程序执行时根据需要由Java虚拟机动态载入相应的类。这种机制使编写动态的分布式应用程序成为可能:我们可以在客户端编写自己的类载入器,而真正执行的程序却存放在本地、局域网或世界另一端的主机上。下面将介绍如何在自己的应用程序中实现Java的动态类载入机制。
  与动态类载入有关的系统类
  ----为支持动态类载入机制,在系统类组java.lang中提供了两个类:Class类和ClassLoader类。
  ----1、类java.lang.Class。在Java虚拟机中,每一个类或接口都是由Class类来操纵的,它不能被显式的实例化,必须用其他方法来获取Class类的对象。动态类载入机制的关键一步在于如何获得指定类的Class类型的对象。相关方法主要有:
  ----public static Class forName(String className)
  ----这是一个静态方法,它获取指定名字的类的Class类型对象,类名可以是象“sun.applet.Applet”这样的字符串,但不能带有路径或网络地址等信息。这是从本地系统中动态载入类的最方便的办法。
  ----public Object newInstance()
  ----这是最重要的一个方法,它建立由Class类型对象描述的指定类的实例。
  ----下面是一个用forName()和newInstance()方法实现动态类载入的代码,share类包含一个接口,详细内容将在第三部分中解释。
  try{
  //根据类名建立Class类型的对象。
  Class cc=Class.forName("类名"));
  //建立被载入类类的实例并强制类型转换,
  值赋给share类型的变量。
  share oo=((share)cc).newInstance();
  //调用该类的方法进行工作。
  }
  catch(Exception ex){
  //如果发生例外,则进行相应处理。
  };
  ----2、类java.lang.ClassLoader。这是一个抽象类,如果打算运用它,必须继承它并重写它的loadClass()方法。其主要方法有:
  ----protected ClassLoader();
  ----这是一个建构元,可以用它建立一个ClassLoader类的实例。注意继承这个类的类必须重写这个方法,而不能使用缺省的建构元。
  ----protected abstract Class loadClass(String name,boolean resolve)
  ----载入指定的类数据,建立Class类型的对象并根据需要解析它。这是一个抽象方法,大家必须在自己的子类中重写这个方法,重写的规则可以参考第三部分的例子。
  ----protected final Class defineClass(byte data[],int offset,int length)
  ----将字节数组中的数据定义为Class类型的对象,字节数组的格式由虚拟机规定。
  ----protected final Class findSystemClass(String name)
  ----根据指定的类名载入类,它会自动在当前目录和环境变量“CLASSPATH”指定的路径中寻找,如果找不到,则会抛出ClassNotFoundException例外。
  ----protected final void resolveClass(Class c)
  ----通过载入与指定的类相关的所有类来解析这个类,这必须在类被使用之前完成。
  扩充ClasslLader类以实现动态类载入
  ----理解动态类载入机制的最好办法是通过例子,下面这个完整的例子由四个类组成,分别解释如下:
  ----1、MyClassLoader类是ClassLoader类的子类,它重写了loadClass方法,实现了将网络上用URL地址指定的类动态载入,取得它的Class类型对象的功能。读者可根据自己载入类的具体方式改写下面的代码。
  import java.io.*;
  import java.util.*;
  import java.net.*;
  public class MyClassLoader extends ClassLoader{
  //定义哈希表(Hashtable)类型的变量,
  用于保存被载入的类数据。
  Hashtable loadedClasses;
  public MyClassLoader(){
  loadedClasses=new Hashtable();
  }
  public synchronized Class loadClass(String className,
  boolean resolve)throws ClassNotFoundException{
  Class newClass;
  byte[]classData;
  //检查要载入的类数据是否已经被保存在哈希表中。
  newClass=(Class)loadedClasses.get(className);
  //如果类数据已经存在且resolve值为true,则解析它。
  if(newClass!=null){
  if(resolve)
  resolveClass(newClass);
  return newClass;
  }
  ----/*首先试图从本地系统类组中载入指定类。这是必须的,因为虚拟机将这个类载入后,在解析和执行它时所用到的任何其他类,如java.lang.System类等,均不再使用虚拟机的类载入器,而是调用我们自制的类载入器来加载。*/
  try{
  newClass=findSystemClass(className);
  return newClass;
  }catch(ClassNotFoundException e){
  System.out.println(className+"is not a system class!");
  }
  //如果不是系统类,
  则试图从网络中指定的URL地址载入类。
  try{
  //用自定义方法载入类数据,
  存放于字节数组classData中。
  classData=getClassData(className);
  //由字节数组所包含的数据建立一个class类型的对象。
  newClass=defineClass(classData,0,classData.length);
  if(newClass==null)
  throw new ClassNotFoundException(className);
  }catch(Exception e){
  throw new ClassNotFoundException(className);
  }
  //如果类被正确载入,
  则将类数据保存在哈希表中,以备再次使用。
  loadedClasses.put(className,newClass);
  //如果resolve值为true,则解析类数据。
  if(resolve){
  resolveClass(newClass);
  }
  return newClass;
  }
  //这个方法从网络中载入类数据。
  protected byte[]getClassData(String className)
  throws IOException{
  byte[]data;
  int length;
  try{
  //从网络中采用URL类的方法
  载入指定URL地址的类的数据。
  URL url=new URL(className.endsWith(".class")?
  className:className+".class");
  URLConnection connection=url.openConnection();
  InputStream inputStream=connection.getInputStream();
  length=connection.getContentLength();
  data=new byte[length];
  inputStream.read(data);
  inputStream.close();
  return data;
  }catch(Exception e){
  throw new IOException(className);
  }
  }
  }
  ----2、由于Java是强类型检查语言,通过网络载入后的类被实例化后只是一个Object类型的对象,虚拟机并不知道它包含那些方法,应从哪个方法开始执行。因此,可以被动态载入的类必须继承某一个抽象类或实现某一个接口,因为父类只能有一个,所以通常用实现特定接口的办法。下面的代码定义了一个接口类share和它的方法start()。
  public interface share{
  public void start(String[]option);
  }
  ----3、TestClassLoader类通过使用MyClassLoader类的loadClass()方法,将指定URL地址的类载入并在本地系统执行它,实现了类的动态载入。注意在执行被载入类的方法前一定要将它进行强制数据类型转换。
  public class TestClassLoader{
  public static void main(String[]args){
  MyClassLoader ll=new MyClassLoader();
  Class cc;
  Object oo;
  String ss="http://kyzser.ydxx/classLoader/Tested.class";
  if(args.length!=0)ss=args[0];
  try{
  System.out.println("Loading class"+ss+"...");
  //使用重写的方法loadClass()载入类数据。
  cc=ll.loadClass(ss);
  System.out.println("Creat instance...");
  //创建Object类型的类实例。
  oo=cc.newInstance();
  System.out.println("Call start()method...");
  //强制类型转换并执行被载入类中的方法。
  ((share)oo).start(args);
  }catch(Exception e){
  System.out.println("Caught exception:"+e);
  }
  }
  }
  ----4、Tested类很简单,可以将它放在任何WEB服务器上,但应注意能动态载入且被执行的类,一定要实现预先定义的接口中的方法。下面的例子实现了接口share的start方法。
  public class Tested implements share{
  public void start(String[]option){
  //填写程序代码。
  }
  }
  动态类载入机制的几点应用
  ----1、开发分布式应用。这对开发远程的客户端应用程序最有用,客户端仅需要安装一些基本的系统和一个能实现动态类载入机制的类,需要本地系统不存在的功能时,仅需要从网络动态载入并执行相应类即可获得特定功能。因为客户端所使用的总是软件的最新版本,所以不再存在软件的升级和维护问题,即实现了所谓的“零管理”模式。
  ----2、对.class文件加密。由于Java的字节码(bytecode)容易被反编译,大部分开发Java应用程序的公司均担心自己的成果被别人不劳而获。其实可以将类文件进行适当的加密处理,执行时使用自己的类载入器进行相应的解密,就可以解决这个问题。
  ----3、使第三方开发者易于扩展你的应用。从前面可知,所有可以被你的类载入器动态载入并被执行的类,必须继承你定义的类或实现你定义的接口,这样,你可以制订一些规则,使其他开发者不必了解你的应用程序也可以扩充功能。
  ----当然,有利必有弊,在网络中使用动态类载入的主要缺陷在于安全性,很难保证不载入不怀好意的代码,这个问题要靠Java的安全管理器和适当的加密算法来解决,已超出本文的讨论范围。