ClassLoader与Java动态调用初步探索

         笔者所工作的一个项目需要使用动态调用方面的技术,这里说的“动态调用”不是普通

的Class.forName(String className),因为这种最常用的调用方式存在一个问题,那就是一旦在调用程

序中加载了class文件,如果你要再修改它并使之立刻生效的话是不可能的。我们需要解决这个问题,找

到可以替代的办法。

         于是笔者在这方面做了一些探索性的工作,并做了一些调查,就目前了解的情况,在Java领域

有两种比较好的解决方案:一种是脚本技术,像Groovy、Beanshell、Python 、Javascript之类;另外一

种就是借助自定义的ClassLoader来实现。先说脚本,笔者在一些项目已经使用过这种技术,总得来说,

脚本语言可以在很多应用场景帮助我们解决问题,对于本文所要求的动态调用,它也是完全可以胜任的,

不过脚本技术最大的缺陷就是不易于调试和查错;我们知道,Java源文件在编译为class文件的时

候,Javac会帮助我们查找出程序语法错误,另外我们可以随时测试Class的方法,而脚本语言就不一样,

它没有这个过程,脚本直接被其引擎执行,并且我们不能对其做单元测试,另外它所代表的业务也是完全

暴露的。正因为脚本技术有上述缺陷,所以在我们的应用中,如果动态调用case比较多,动态调用中被调

用代码量很大而且与业务结合的比较紧密的话,笔者建议不要使用脚本来完成动态调用,开发和调试的工

作量非常大,而且运行也不一定高效。笔者在另外一篇文章中提到了脚本技术的应用,读者有兴趣可以去

看看。
         上面说了一大堆没有营养的话,主角可以登场了,正如标题所说,我们现在要探讨的是Java

ClassLoader(后面用CL简称)是否可以帮助我们解决动态调用的问题。笔者在这里就不用说CL的概念,网

上有太多这方面的文章,我们可以检索。JVM中可能存在多个CL,每个CL拥有自己的NameSpace,CL之间有

父子关系,一个CL只能拥有一个class对象类型的实例,但是不同的CL可能拥有相同的class对象实例;CL

还有个特点,就是一旦父级CL已经load了某个Class,那么子CL是不能再load该CL的,我相信这个特性给很

多开发者已经制造了很多困扰,特别是基于APP server的开发。我们就以Tomcat为例说明一下,
Tomcat Server的ClassLoader结构如下:

        +-----------------------------+
        |         Bootstrap           |
        |             |               |
        |          System             |
        |             |               |
        |          Common             |
        |         /      \            |
        |     Catalina  Shared        |
        |               /    \        |
        |          WebApp1  WebApp2   |
        +-----------------------------+

 

我们开发的web应用是WebApp2,我们需要版本为1.3的xml-apis.jar,于是我们把它放

在$TOMCAT_HOME$\webapps\WebApp2\WEB-INF\lib目录下,但是tomcat安装的时候,已经放置了一个版本

为1.2的xml-apis.jar,因为Common CL是WebApp2 CL的爷爷,所以爷爷权利大一点,于是WebApp2就只能使

用1.2的XML API了,与它需要的1.3不匹配,矛盾就产生了,WebApp2运行就会有问题。虽然这与本文主题

关系不大,在这里谈谈主要是让我们注意这种关系,在开发自定义的CL的时候可以考虑到这些问题。

          好,现在我们开始正式“手工”探索,我们先写一个简单的Class,然后用一个自定义的CL来引导

它。切记,这个测试的Class不能被运行程序找到,即不能放入到运行程序的ClassPath中,否则测试没有

意义,如果放入到运行程序的ClassPath中,那么就会被自定义的CL的父CL所引导。
Class(Test1)的源代码如下:

package com.test;
import java.util.*;
public class Test1
{
 public static String STR_CONSTANT = "good";
 public Test1()
 {
    System.out.println("**********constructor

Test1,*****************:"+STR_CONSTANT);
 }
 
}
CL(Test1ClassLoader)的源代码如下:

import java.io.*;
/*
 * author:Jean(lxjchengcu@gmail.com)
 */
public class Test1ClassLoader extends ClassLoader {
 private ClassLoader _parent;

 public Test1ClassLoader(ClassLoader parent) {
  _parent = parent;
 }
 // get bytes from file
 private byte[] getBytes(String filename) throws IOException {
  // get file size
  File file = new File(filename);
  long len = file.length();
  byte raw[] = new byte[(int) len];
  // open file
  FileInputStream fin = new FileInputStream(file);
  int r = fin.read(raw);
  if (r != len)
   throw new IOException("Can't read all, " + r + " != " + len);
  fin.close();
  return raw;
 }
 public synchronized Class loadClass(String name)
  throws ClassNotFoundException
 {
  return loadClass(name,false);

 }
 // load class
 public synchronized Class loadClass(String name, boolean resolve)
   throws ClassNotFoundException
 {
  Class clas = null;
  clas = findLoadedClass(name);
  if (clas == null) {
   //System.out.println("not find loaded class");
  }
  if (clas == null) {
   try {
    clas = _parent.loadClass(name);
   } catch (Exception e) {
    //e.printStackTrace();
   }
  }
        if (clas == null)
        {
         String fileStub = name.replace('.', '/');
      String classFilename = fileStub + ".class";
   try {
    byte raw[] = getBytes("D:/test/" + classFilename);
    clas = defineClass(name, raw, 0, raw.length);
                                System.out.println("define class name is "+name);
   } catch (IOException ie) {
   }
        }
  if (clas == null) {
   clas = findSystemClass(name);
  }
  if (resolve && clas != null)
  {
   resolveClass(clas);
  }
  if (clas == null)
   throw new ClassNotFoundException(name);
  return clas;
 }
 public static void main(String args[])
  throws Exception
 {
  Test1ClassLoader test1CL = new Test1ClassLoader(Test1ClassLoader.class
    .getClassLoader());
  Class cls = Class.forName("com.test.Test1",true,test1CL);
  cls.newInstance();
 }
}

运行CL,控制台输出如下:
define class name is com.test.Test1
**********constructor Test1,*****************:good

这证明Test1已经被Test1ClassLoader引导。读者可能有疑问,为什么这里构造CL,需要设置父CL呢,这

是因为运行所依赖的Class需要父级以上的CL引导,使用-verbose:class运行参数我们就可以从控制台输出

清楚地得出所有Class的装载过程。

         现在我们要做测试是在程序运行期间修改Test1的内容,然后看CL能否引导最新的Class,为了完

成这个测试,我们需要调整Test1ClassLoader的main方法的代码:
public static void main(String args[])
  throws Exception
 {
  Test1ClassLoader test1CL = new Test1ClassLoader(Test1ClassLoader.class
    .getClassLoader());
  while(true)
  {
   Class cls = Class.forName("com.test.Test1",true,test1CL);
   cls.newInstance();
   Thread.sleep(5000);
   System.out.println("*******************************sleep

over*******************************");
  }
 }
在Thead sleep期间修改Test1的static变量STR_CONSTANT为bad,然后看输出结果:
define class name is com.test.Test1
**********constructor Test1,*****************:bad
*******************************sleep over*******************************
**********constructor Test1,*****************:bad
*******************************sleep over*******************************
**********constructor Test1,*****************:bad

怎么回事?我们需要的结果是:
define class name is com.test.Test1
**********constructor Test1,*****************:bad
*******************************sleep over*******************************
**********constructor Test1,*****************:good
*******************************sleep over*******************************
**********constructor Test1,*****************:good
这是因为cls已经缓存在起来了,所以每次loadClass的时候,就会先从缓存中读取出来,而不是从磁盘上

,既然这样,那我们换一个CL试试:
public static void main(String args[])
  throws Exception
 {
  Test1ClassLoader test1CL = null;
  while(true)
  {
   test1CL = new Test1ClassLoader(Test1ClassLoader.class
     .getClassLoader());
   Class cls = Class.forName("com.test.Test1",true,test1CL);
   cls.newInstance();
   Thread.sleep(5000);
   System.out.println("*******************************sleep

over*******************************");
  }
 }

重新运行,在Thead sleep期间修改Test1的static变量STR_CONSTANT为bad,然后看输出结果:
define class name is com.test.Test1
**********constructor Test1,*****************:good
*******************************sleep over*******************************
define class name is com.test.Test1
**********constructor Test1,*****************:bad
*******************************sleep over*******************************
define class name is com.test.Test1
**********constructor Test1,*****************:bad
*******************************sleep over*******************************

好,成功完成既定任务,也就是说我们需要使用另一个CL来引导更改的Class(当然,还可以有其他办法

,比如我们loadClass的时候发现name=Test1,就不从缓存中获取Class,而是读stream来构造Class实例,但

笔者认为这种hardCode方法不妥也不通用)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值