笔者所工作的一个项目需要使用动态调用方面的技术,这里说的“动态调用”不是普通
的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 2 SDK.pdf`可能涉及的是Java与其他工具和技术的集成,如与微软Office的交互、使用Visual Studio .NET管理Java应用、Ant构建工具的使用以及对Java 2 SDK的深入理解,这些都可能间接地涉及到ClassLoader的...
总结来说,Java ClassLoader的定制是一项强大的技术,它可以让我们实现动态加载代码、构建灵活的插件系统等功能。然而,这也需要开发者对Java的内存管理和类加载机制有深入的理解,以便正确且安全地使用。通过研究...
下面将详细介绍如何在Java程序中调用外部jar文件。 首先,了解Java类加载器的工作原理是必要的。默认情况下,Java的系统类加载器会从类路径(CLASSPATH)中加载类,包括JRE自身的库、应用的主类路径以及任何用户...
在加载流程中,当运行一个程序的时候,JVM 首先启动 bootstrap classloader,该 ClassLoader 加载 Java 核心 API,然后调用 ExtClassLoader 加载扩展 API,最后 AppClassLoader 加载 CLASSPATH 目录下定义的 Class,...
让Java支持热加载是个不错的想法。如何做到的呢? 1. 定义好接口和实现类 2. 让代理类通过反射的方式调用实现类,对外暴露的是代理类。 3. 自定义URLClassLoader。检查实现类.class文件的...Java自定义classloader;
与exe4j等工具相比,应用程序类加载器提供了一种更标准、跨平台的方式来启动Java程序。通过配置类路径,开发者可以自定义加载顺序和加载来源,例如从网络、文件系统或特定的jar中加载类。 **配置灵活性**: 在开发...
在Java与MongoDB的交互中,我们可以利用MongoDB的Java驱动程序来连接数据库并查询Groovy脚本。MongoDB不仅支持存储文档数据,还允许存储JavaScript代码,这对于我们的需求非常适用。我们可以编写一个Java方法,从...
### Java ClassLoader与ClassPath详解 #### 一、概述 在Java编程中,类加载机制是十分关键的一个环节。类加载器(`ClassLoader`)负责将编译后的`.class`文件加载到Java虚拟机(JVM)中执行,而类路径(`ClassPath...
总结来说,Java ClassLoader机制是Java平台的基础,它使得程序能够动态地加载和管理类。而在OSGi这样的模块化环境中,ClassLoader机制得到了进一步的发展,实现了更加精细的类加载控制和更好的模块隔离。理解并掌握...
1. **自定义ClassLoader**:Java允许我们创建自定义的ClassLoader,这通常用于实现动态加载类的需求。自定义ClassLoader需要重写`findClass()`或`loadClass()`方法。`loadClass()`方法是类加载的入口,它会调用`find...
总之,自定义ClassLoader是Java应用程序中动态加载外部jar包的关键技术。通过理解ClassLoader的工作原理和正确实现加载逻辑,我们可以灵活地扩展应用程序的功能,支持热更新和插件化等高级特性。在阅读和实践"定义...
2. **JNI接口定义**:在Delphi中,我们需要创建一个动态链接库(DLL),该库将作为Delphi与Java的接口。首先,我们需要定义Java端的JNI函数,这通常是在Java的`.java`文件中,使用`native`关键字声明。然后,通过`...
Java的`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口可以创建动态代理类,用于在调用实际目标对象的方法前或后添加额外的行为,适合用于插件的拦截和增强。 5. **OSGi(Open Services ...
总的来说,Java ClassLoader机制是Java平台的核心特性之一,它使得程序具有动态性和可扩展性。深入理解ClassLoader的工作原理,不仅有助于提升编程能力,还能帮助开发者在面对复杂系统设计时做出更明智的决策。
为了确保动态加载的类与已加载的类之间能够正确地交互,Java虚拟机必须执行一系列的链接操作。这些操作包括验证类文件格式、准备类数据结构以及解析符号引用等。这些步骤确保了即使是在动态环境中加载的类也能保持...
Java中的类加载器(ClassLoader)是Java虚拟机(JVM)的一个重要组成部分,它负责将类的.class文件从文件系统或者网络中加载到内存中,并转换为对应的Class对象。类加载器的工作流程主要包括加载、验证、准备、解析...