`
lxjchengcu
  • 浏览: 23503 次
  • 性别: Icon_minigender_1
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

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方法不妥也不通用)。

分享到:
评论

相关推荐

    破解java加密的ClassLoader.java,在classloader植入破解代码

    破解java加密的ClassLoader.java,在classloader植入破解代码

    java classloader

    深入Java 2 SDK.pdf`可能涉及的是Java与其他工具和技术的集成,如与微软Office的交互、使用Visual Studio .NET管理Java应用、Ant构建工具的使用以及对Java 2 SDK的深入理解,这些都可能间接地涉及到ClassLoader的...

    Java ClassLoader定制实例

    总结来说,Java ClassLoader的定制是一项强大的技术,它可以让我们实现动态加载代码、构建灵活的插件系统等功能。然而,这也需要开发者对Java的内存管理和类加载机制有深入的理解,以便正确且安全地使用。通过研究...

    java调用外部jar文件

    下面将详细介绍如何在Java程序中调用外部jar文件。 首先,了解Java类加载器的工作原理是必要的。默认情况下,Java的系统类加载器会从类路径(CLASSPATH)中加载类,包括JRE自身的库、应用的主类路径以及任何用户...

    Java ClassLoader学习总结

    在加载流程中,当运行一个程序的时候,JVM 首先启动 bootstrap classloader,该 ClassLoader 加载 Java 核心 API,然后调用 ExtClassLoader 加载扩展 API,最后 AppClassLoader 加载 CLASSPATH 目录下定义的 Class,...

    Java实现热加载完整代码;Java动态加载class;Java覆盖已加载的class;Java自定义classloader

    让Java支持热加载是个不错的想法。如何做到的呢? 1. 定义好接口和实现类 2. 让代理类通过反射的方式调用实现类,对外暴露的是代理类。 3. 自定义URLClassLoader。检查实现类.class文件的...Java自定义classloader;

    java应用程序类加载器,ClassLoader for java Application

    与exe4j等工具相比,应用程序类加载器提供了一种更标准、跨平台的方式来启动Java程序。通过配置类路径,开发者可以自定义加载顺序和加载来源,例如从网络、文件系统或特定的jar中加载类。 **配置灵活性**: 在开发...

    Java调用Groovy,实时动态加载数据库groovy脚本

    在Java与MongoDB的交互中,我们可以利用MongoDB的Java驱动程序来连接数据库并查询Groovy脚本。MongoDB不仅支持存储文档数据,还允许存储JavaScript代码,这对于我们的需求非常适用。我们可以编写一个Java方法,从...

    java classloader classpath 张孝祥

    ### Java ClassLoader与ClassPath详解 #### 一、概述 在Java编程中,类加载机制是十分关键的一个环节。类加载器(`ClassLoader`)负责将编译后的`.class`文件加载到Java虚拟机(JVM)中执行,而类路径(`ClassPath...

    java ClassLoader机制及其在OSGi中的应用

    总结来说,Java ClassLoader机制是Java平台的基础,它使得程序能够动态地加载和管理类。而在OSGi这样的模块化环境中,ClassLoader机制得到了进一步的发展,实现了更加精细的类加载控制和更好的模块隔离。理解并掌握...

    使用classloader动态加载Class

    1. **自定义ClassLoader**:Java允许我们创建自定义的ClassLoader,这通常用于实现动态加载类的需求。自定义ClassLoader需要重写`findClass()`或`loadClass()`方法。`loadClass()`方法是类加载的入口,它会调用`find...

    定义ClassLoader调用外部jar包

    总之,自定义ClassLoader是Java应用程序中动态加载外部jar包的关键技术。通过理解ClassLoader的工作原理和正确实现加载逻辑,我们可以灵活地扩展应用程序的功能,支持热更新和插件化等高级特性。在阅读和实践"定义...

    Delphi调用Java类和包源代码

    2. **JNI接口定义**:在Delphi中,我们需要创建一个动态链接库(DLL),该库将作为Delphi与Java的接口。首先,我们需要定义Java端的JNI函数,这通常是在Java的`.java`文件中,使用`native`关键字声明。然后,通过`...

    java调用插件代码.rar

    Java的`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口可以创建动态代理类,用于在调用实际目标对象的方法前或后添加额外的行为,适合用于插件的拦截和增强。 5. **OSGi(Open Services ...

    理解Java ClassLoader机制

    总的来说,Java ClassLoader机制是Java平台的核心特性之一,它使得程序具有动态性和可扩展性。深入理解ClassLoader的工作原理,不仅有助于提升编程能力,还能帮助开发者在面对复杂系统设计时做出更明智的决策。

    Java ClassLoader原理

    为了确保动态加载的类与已加载的类之间能够正确地交互,Java虚拟机必须执行一系列的链接操作。这些操作包括验证类文件格式、准备类数据结构以及解析符号引用等。这些步骤确保了即使是在动态环境中加载的类也能保持...

    java中classLoader的使用

    Java中的类加载器(ClassLoader)是Java虚拟机(JVM)的一个重要组成部分,它负责将类的.class文件从文件系统或者网络中加载到内存中,并转换为对应的Class对象。类加载器的工作流程主要包括加载、验证、准备、解析...

Global site tag (gtag.js) - Google Analytics