`
kakaluyi
  • 浏览: 439846 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

在Java中动态加载class

阅读更多
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文件

    NULL 博文链接:https://zheng12tian.iteye.com/blog/1488813

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

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

    java热加载Class文件

    java热加载Class文件

    java热加载Class文件.zip

    java热加载Class文件.zipjava热加载Class文件.zipjava热加载Class文件.zip

    动态编译字符串成java,并且添加class到jvm

    动态编译字符串成java,并且添加class到jvm

    java 类加载器 class loader

    java 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loader

    Java中Class类工作原理详解

    在运行时,当我们想生成这个类的对象时,运行这个程序的 Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载。如果尚未加载,JVM就会根据类名查找.class文件,并将其载入。 一旦某个类的Class对象被载入内存...

    Java Class.forName()无法加载类

    NULL 博文链接:https://listen-raining.iteye.com/blog/1894456

    java class reload

    在在不重启tomcat的情况下重新加载类 的一个小例子

    Spring bean 动态注册,jar包热替换

    Spring bean 一般通过配置文件和注解进行加载,如果要实现jar或class...测试示例中是spring boot 的部分代码,动态加载的内容为接口实现类,且初始化时加载本地的实现类,动态加载后改为非程序加载目录中的jar实现类。

    Java代码执行漏洞中类动态加载的应用1

    代码中创建了 U 类继承 ClassLoader ,然后定义个名为 g 的法,接收字节数组类型的参数并调类的 defineClass 动态解析字节码返回 Cla

    Java动态编译Java代码,运行在内存中,并执行

    通过java的ToolProvider创建JavaCompile,用来执行class源文件 * 4.创建DiagnosticCollector用来执行获取执行失败的错误结果 * 5.添加动态执行的编译环境 options 是个集合,添加内容,字符集,classpath等 * 6....

    Java 动态加载jar和class文件实例解析

    主要介绍了Java 动态加载jar和class文件实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

    java class加密保护工具

    运行时,要能正确的加载加密后的class文件, 必须使用我们提供的动态链接库classloader.dll(wndows操作系统)或者libclassloader.so(Linux、Unix操作系统)。 执行java时带上参数-agentlib:<动态文件所在路径>\...

    JAVA类加载

    Java 语言是一种具有动态性的解释型编程语言,当指定程序运行的时候, Java 虚拟机就将编译生成的 .class 文件按照需求和一定的规则加载进内存,并组织成为一个完整的 Java 应用程序。 Java 语言把每个单独的类 ...

    Java之——类热加载

    java 的类动态加载实现,入口在MsgHandler 通过修改MyManager类中的输出值可用动态编译对应的class,然后替换后查看效果。

    javac编译成功用java运行class文件出现“找不到或无法加载主类”的问题解决起来很简单.doc

    在 Java 开发中,经常会遇到一个问题,即 javac 编译成功后,用 java 运行 Class 文件却出现“找不到或无法加载主类”的错误信息。这种问题看似很复杂,但实际上解决起来非常简单。下面我们将详细讲解该问题的解决...

    java class加密

    Java Class加密器是广州市敏创信息科技有限公司在保护自己的Java软件过程中积累的一套实用工具,可以有效地对Java Class进行加密,达到保护自己知识产权的目的。 工具对编译后的二进制字节码进行加密,当Tomcat等...

    JVM加载class文件的原理机制.pdf

    JVM加载class文件的原理机制是Java虚拟机中一个非常重要的组件,负责将class文件加载到内存中,以便Java程序的执行。下面是JVM加载class文件的原理机制的详细介绍: 类加载的原理 在Java中,所有的类都必须被加载...

    在可执行jar中载入第三方jar的几个解决方法

    以下情景是很多Java开发人员经常碰到的:在开发,调试阶段,通过在CLASSPATH中设置第三方的jar包路径,自己编写的class工作正常.开发完毕,部署的时候,将开发的class打成一个可执行jar包,会发现通过Class.forName...

Global site tag (gtag.js) - Google Analytics