Java的一个强大的特性是能够动态加载一个给定名称的类,而事先不需要指导这个类的名字。这个特性使得Java的开发人员能够构造一个不需要重新编译即可扩展和修改的灵活动态的系统,在Java中,动态加载通常是调用类java.lang.Class的forName方法来实现;然而,在一个jar包中调用Class.forName会出现一些奇怪的错误。
下面的内容需要读者具备一定的java知识、ClassLoader知识、编译原理和面向对象的知识。
问题描述:
例如,下面的代码在Main方法中调用ClassLoader来加载一个命令行传入的class.
LoaderTest.java文件
package aa
public class LoaderTest
{
public static void main(String[] args)
{
LoadClass(args[0]);
}
public static void LoadClass(String clsName)
{
try
{
beLoaded bl =
(beLoaded)Class.forName(clsName).newInstance();
bl.PrintInfo();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
beLoaded.java文件
package aa;
public class beLoaded
{
public void PrintInfo()
{
System.out.println("I am be loaded!");
}
}
上面的代码在正常情况下非常好使,并且使得整个系统可以同具体的java类分离开来,只需要传入一个class的类名即可完成功能调用。而且从扩展的角度来说,定义一些从beLoaded类上继承下来的类,并将类名通过参数传入系统,即可实现各种不同的功能类的调用,非常方便。
在命令行上键入如下命令:
> java aa.LoaderTest aa.beLoaded
屏幕会输出下面的内容:
I am be loaded!
下面我们创建一个beLoaded的子类,类名叫做beLoadedChild,代码如下:
package aa;
public class beLoadedChild extends beLoaded
{
public void PrintInfo()
{
System.out.println("I am be loaded and I am Child");
}
}
在命令行上键入如下命令:
> java aa.LoaderTest aa.beLoadedChild
屏幕会输出下面的内容:
I am be loaded and I am Child
通过上面的例子我们可以看出,只要设计好LoaderTest这个类和beLoaded类,就可以实现系统的扩展性,对不同的功能目标调用不同的beLoaded的子类,来完成不同的应用,系统十分的灵活和方便。
在Java中,为了更好的管理Java的Class,Java允许开发者将一些相关的Class打包放到.jar文件中去,使得系统管理非常的方便,因此我们将上面的aa.LoaderTest和aa.beLoaded这两个类打包到aa.jar中去。将.jar文件放到JAVA_HOME/jre/lib/ext目录中去。
因为beLoadedChild是子类,所以我们将这个类放在外面,可以随时地修改这个子类而不用重新编译LoaderTest两个类。这里我们将beLoadedClass.class放在当前目录下的aa目录下,具体方法如下:
> java aa.LoaderTest aa.beLoaded
屏幕会输出下面的内容:
I am be loaded!
上面的beLoaded类在.jar包中存在,所以执行起来没有问题,我们看一看下面的执行情况:
> java aa.LoaderTest aa.beLoadedChild
屏幕会输出下面的内容:
java.lang.ClassNotFoundException: aa.beLoadedChild
at java.net.URLClassLoader$1.run(URLClassLoader.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:141)
at aa.LoaderTest.LoadClass(LoaderTest.java:25)
at aa.LoaderTest.main(LoaderTest.java:18)
系统提示找不到aa.beLoadedChild这个类,可是这是不应该出现的问题,因为beLoadedChild类就在当前目录的aa目录下中,从package的角度来讲是没有问题的,但是问题出在什么地方呢?
问题分析
分析这个问题,我们可以从两个方面入手,一个是.jar的问题,另一个是java虚拟机的加载机制问题,这两方面最终会统一到java的加载机制问题。
为什么说是.jar的问题呢?首先从运行情况的比较来看,在所有的类都不在.jar文件中时,程序执行没有问题,在打包以后,执行.jar文件中包含的class没有问题,而.jar文件之外的class就出现了ClassNotFound的错误,通过这个现象我们可以认为问题是由.jar打包而产生的。
但是反过来,sun公司提供的jdk都是以.jar的形式发放的,他们的.jar包中肯定也会存在动态加载class的问题,比较典型的是jdbc,在使用jdbc时都要使用Class.forName()来加载数据库的驱动,为什么这时没有出现问题呢?
所以上面的问题表明sun公司的jar包中在动态加载类时肯定进行了特殊的处理或者调用,才使得加载jdbc驱动没有产生ClassNotFound的错误。
看来问题的根源还是在java虚拟机的加载机制上了。
为了理解这个问题,你需要理解一些Java ClassLoader模型的基础知识以及JDK1.1同Java2之间的差别。
Java2 ClassLoader Delegation Model.(Java2 ClassLoader委托模型)
Java2 ClassLoader模型是一种“delegating parent(委托给父ClassLoader)”模型,这意味着当一个ClassLoader被要求加载一个class时,它首先要求它的parent ClassLoader来加载这个类。只有当它的parent 没有加载过这个类并且也不能加载这个类时,它才自己处理这个加载请求。这就是说ClassLoader是一个类似于树的形式的结构,”bootstrap”这个ClassLoader是这棵树的根节点。
任何一个用户创建的ClassLoader必须拥有一个parent ClassLoader,如果创建时没有提供parent,ClassLoader的创建者假定其parent 是”system”或者ClassLoader。
在JVM中被加载的每一个Class都会同加载它的ClassLoader保持有隐含的关联,这个关联我们可以通过Class的方法getClassLoader来找到。每一个Class同一个并且只能是一个ClassLoader相关联,并且这个关联不能通过改变ClassLoader的引用来指向另一个ClassLoader。
通常程序员会直接调用ClassLoder的loadClass方法,JVM通常是隐式的调用该方法。在JDK1.0和1.1模型中,ClassLoader直接覆盖了loadClass方法来负责实现该方法。而在JDK1.2中,loadClass方法调用它的parent的loadClass方法来检查其parent 是否已经加载了class,只有当它的parent加载class失败时,它自己才有机会加载class.
通过上面的分析我们可以大致的猜测到,直接通过Class.forName来动态加载class,如果该调用在.jar中的话,那么它的加载范围就在整个.jar中,而不是整个java虚拟机的classpath中。根据java的ClassLoader的工作原理我们可以知道,java的ClassLoader是一个继承的过程,整个java虚拟机拥有一个classloader,而其下的每一个.jar文件应该对应一个ClassLoader,这个ClassLoader的加载范围就是整个.jar文件中的范围,所以前面我们做的例子中当加载.jar文件中的class时,就可以正常通过并执行,而加载.jar之外的class时,由于ClassLoader的范围中没有这个class,所以产生ClassNotFound的错误。
为了证实我们上面所做的猜测,我们来做一个程序试验一下:
前面说到java的ClassLoader是以一个树的形式存在的,通过这个特点我们可以考虑在加载class时不使用当前类的ClassLoader,而是使用整个应用程序的ClassLoader,也就是树的根节点来加载类,这样肯定就没有问题了;或者我们不用根节点,使用当前类的ClassLoader的parent ClassLoader也许也同样能够解决问题;再或者我们使用当前进程或者线程的ClassLoader也应该能够解决这个问题。
解决方法:
下面我们来做个试验,将LoaderTest中的方法修改一下,使用当前线程的ClassLoader来加载class,看看是否能够成功,代码如下:
package aa;
public class LoaderTest
{
public static void main(String[] args)
{
LoadClassEx(args[0]);
}
public static void LoadClass(String clsName)
{
try
{
beLoaded bl =
(beLoaded)Class.forName(clsName).newInstance();
bl.PrintInfo();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void LoadClassEx(String clsName)
{
try
{
Thread t = Thread.currentThread();
ClassLoader cl = t.getContextClassLoader();
beLoaded bl = (beLoaded)cl.loadClass(clsName).newInstance();
bl.PrintInfo();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
编译后,打包,并将.jar文件放入到运行目录中去,然后在命令行输入下列命令:
> java aa.LoaderTest aa.beLoaded
屏幕会输出下面的内容:
I am be loaded!
继续输入下列命令:
> java aa.LoaderTest aa.beLoadedChild
屏幕会输出下面的内容:
I am be loaded and I am Child
这样我们的猜测和运行结果完全一致,在.jar包中的class拥有了加载任意位置的class文件的能力了。
我们还可以使用ClassLoader cl = ClassLoader.getSystemClassLoader();来获得系统级的ClassLoader,以获得更广泛的加载范围。
分享到:
相关推荐
这篇博客“Java类动态加载(一)——java源文件动态编译为class文件”可能主要探讨了如何在运行时将Java源代码(.java)编译成对应的字节码文件(.class),并将其加载到Java虚拟机(JVM)中。以下是对这个主题的详细解析...
让Java支持热加载是个不错的想法。如何做到的呢? 1. 定义好接口和实现类 2. 让代理类通过反射的方式调用实现类,对外暴露的是代理类。...Java动态加载class; Java覆盖已加载的class; Java自定义classloader;
在Java编程中,动态编译代码并热加载类是一项重要的技术,它允许程序在运行时修改或添加新的类,而无需重启应用。这种能力对于快速迭代开发、调试和性能优化非常有用。本主题将深入探讨Java中的动态编译与热加载机制...
总之,Java热加载Class文件是提高开发效率的有效手段,它通过动态替换和更新类,使得开发者能够在不重启JVM的情况下观察代码更改的效果。了解并掌握这项技术,对于提升Java开发者的生产力具有重要意义。
在Java编程语言中,动态加载jar文件是一种关键的特性,它允许程序在运行时加载新的类库或组件,而不是在编译时静态地链接。这种技术对于实现插件式开发或者模块化系统至关重要,因为它提供了灵活性和可扩展性。下面...
总之,理解并掌握如何使用ClassLoader动态加载Class是Java开发中的重要技能,它能帮助我们构建更灵活、可扩展的系统。在实现过程中,要兼顾性能、安全和可维护性,合理设计类加载策略,确保代码的高效运行。
在Android开发中,动态加载Class是一项重要的技术,它允许应用程序在运行时加载未知或更新的类,从而提高软件的灵活性和可扩展性。这在处理插件化、热修复或者模块化开发时尤为常见。本篇文章将深入探讨如何在...
在Java编程中,动态编译字符串成Java代码并将其加载到JVM(Java虚拟机)是一种高级技巧,常用于运行时代码生成、元编程或插件系统等场景。这一技术的核心在于利用Java的反射API和Java Compiler API。下面将详细阐述...
例如,`MemoryClassLoader.java`可能就是一个自定义类加载器的实现,它可以在内存中动态加载或更新类。 **JarinJAR**是一种打包技术,它可以将多个JAR文件打包成一个大的JAR文件。在热加载场景下,JarinJAR使得在...
在Java编程语言中,动态加载jar包是一种常见的需求,它允许程序在运行时根据需要加载新的功能或更新现有的模块,增强了软件的灵活性和可扩展性。动态加载jar包技术主要涉及Java的反射机制、类加载器和插件系统。下面...
当我们谈论“Java实现的面向接口的动态加载驱动的方法”,我们实际上在讨论如何在运行时动态地加载实现了特定接口的类,以便于在不修改原有代码的情况下,插入新的功能或替换旧的实现。 以MySQL数据库驱动为例,...
在Java编程中,类动态加载是一项重要的特性,它允许程序在运行时加载未知或自定义的类。这个特性在某些场景下非常有用,如插件系统、热部署以及安全相关的利用。本文主要探讨了Java代码执行漏洞中类动态加载的应用,...
在Java编程中,动态加载JAR或ZIP包是一项重要的技术,它允许程序在运行时根据需求加载外部库,而不是在编译时静态地链接。这种技术对于实现插件化、模块化系统,或者处理频繁更新的组件非常有用。下面将详细讲解如何...
Java动态加载数据库驱动是解决在同一个系统中需要与多种数据库版本进行交互问题的一种技术手段。在实际开发中,由于各种项目可能需要连接不同类型的数据库,如Oracle、SQL Server等,或者同一种数据库的不同版本,...
在Java中,动态加载jar包的核心在于使用`java.lang.ClassLoader`类或其子类。ClassLoader是Java虚拟机(JVM)的一部分,负责将类的字节码加载到JVM中并转换为Class对象。默认情况下,JVM会使用系统类加载器来查找和...
2. **加载类**:使用`Class.forName()`方法根据配置文件中的类名动态加载类。 3. **创建对象**:调用`clazz.newInstance()`方法创建类的一个新实例。 4. **设置字段值**:通过`Field`类的`set()`方法为对象的指定...
在Java编程语言中,动态加载类机制是一种强大的功能,它允许程序在运行时根据需要加载新的类或资源,而不是在编译时确定所有类。这种技术对于提高软件的灵活性、可扩展性和模块化至关重要,特别是在大型系统和插件式...
Groovy代码可以被Java编译器理解,并且在运行时,GroovyShell或GroovyClassLoader等工具能够即时编译和执行Groovy脚本,这为动态加载和执行提供了便利。 当我们需要在Java程序中调用Groovy脚本时,可以使用以下步骤...
- JRebel通过替换Java虚拟机(JVM)的类加载机制,使得已加载的类可以被动态替换,达到热重载的效果。 3. **Tomcat自带的Class reloading** - Tomcat在开发模式下,可以通过设置`reloadable=true`在`conf/server....
Java热加载Class文件技术是一种在不重启应用服务器的情况下更新或替换正在运行的Java类的方法,这对于开发者来说是一项非常实用的功能,因为它极大地提高了开发效率。在传统的开发过程中,修改代码后通常需要停止、...