0引言
Java体系结构包括Java语言、Javaclass文件、JavaAPI和Java虚拟机(JVM)4大部分,核心是JVM。JVM的主要特征是支持Java动态类加载,允许在程序运行时动态加载程序组件,而不影响其它功能模块的正常运行。本文介绍了Java类加载器的体系结构,分析Java动态类加载机制的原理,讨论了Java动态类加载机制的实现,并结合C/S结构的学生实验教学系统展示Java动态类加载机制的实际应用。并指出Java动态类加载机制在实现安全性、代码保护等其它领域的应用。
1Java类加载器的体系结构
JVM有灵活的类装载器结构,一个应用程序中允许多个类加载器存在,并可以用自定义类加载方式。JVM中类加载器分两种类型:BootstrapClassLoader和用户自定义类加载器。图1描述Java类加载器的体系结构,BootstrapClassLoader是JVM实现的一部分,系统中惟一的、用编写虚拟机的语言编写的类加载器。若JVM用C/C++在操作系统上实现,则BootstrapClassLoader是用C/C++实现的一个部分。BootstrapClassLoader用默认方式从classpath中加载Java运行环境提供的所有核心类。这些类是所有应用程序必须的,因此不是“即用即装”而是首先装入并永驻JVM,直至JVM退出。
BootstrapClassLoader除加载核心类外,还加载两个Java运行环境提供的用户类加载器:ExtClassLoader和AppClass-Loader,核心类之外的类是由这两个类加载器加载的。BootstrapClassLoader只力口载jre/classes中的核心类。ExtClassLoader和AppClassLoader由JVM创建并继承自ClassLoader且由Java语言实现故被称为用户类加载器。ExtClassLoader加载jre/lib/ext中所有类,此目录下是Java运行环境所需要的扩展类;AppClass-Loader加载classpath指定的路径中用户应用程序所需要的类。
自定义类加载器可使用自定义方式加载类型。例如,从网络或数据库中获得class文件。尽管BootstrapClassLoader是JVM的本质(自定义类加载器不是),但自定义类加载器能够用Java语言编写,能被编译成class文件、被JVM加载,还能被实例化,实际是运行的Java应用程序可执行代码的一部分。自定义类加载器不必在编译的时候就知道运行中的Java应用程序最终加入的所有类,使得在运行时扩展Java应用程序成为可能。当它运行的时,应用程序能够决定它需要额外的类,能够决定是使用一个或是更多的用户定义的类加载器来加载。
Java动态类加载机制的原理
JVM运行时至少会产生3个类加载器:BootstrapClassLoa-der,ExtClassLoader和AppClassLoader,为协调多个类加载器工作,Java对类加载器进行了分工和分级,不同级别的类加载器负责不同的类加载,并引入“Parent”概念,即“双亲”JVM通过基于类加载器层次关系的“双亲委托”3]机制实现按需加载。
Java核心类库提供抽象类ClassLoader,所有自定义类加载器必须继承并实例化该类。自定义类加载器也依赖其它类加载器——至少依赖JVM启动时创建的载器BootstrapClassLoader来实现一些类加载请求。类加载器请求另一个类加载器来加载类的过程被形式化,即“双亲委托”模型。除了启动类加载器外的类加载器有且只有一个“双亲”若没有显式地指定“双亲”,系统根据情况为它们分配一个默认的“双亲”。例如,ExtClassLoader和AppClassLoader的“双亲”就是BootstrapClassLoader。启动类加载器不是用Java语言编写的,Java规定,BootstrapClassLoader用null表示。自定义类加载器试图以常用的方式加载类型前,默认地将任务“委派”给它的“双亲”一一请求“双亲”来加载这个类型。“双亲”再请求它自己的“双亲”来加载这个类型[7]。委派过程一直向上,直到BootstrapClassLoader,通常BootstrapClassLoader是委派链中最后一个。若一个类的“双亲”类加载器有能力,则这个类加载器试图自己加载这个类型。如图2所示,请求加载一个类型时,首先自底向上,逐一检查此类加载器是否成功加载所请求的类型,若己成功加载了类型,则直接返回类型的引用实例。若直到启动类加载器也未成功加载类型,则自顶向下,逐一尝试加载类型,直到CustomClassLoader。若CustomClassLoader没成功加载类型则抛出ClassNotFoundException。
JVM加载本地class文件的工作被分配到多个类加载器中,BootstrapClassLoader加载核心JavaAPI的class文件,Cus-tomClassLoader负责加载其它class文件。例如,应用程序运行的class文件(第4节中的学生实验教学系统即是对从数据库中获得Java应用程序源代码,调用Java编译器编译,然后自定义加载)、用于安装或下载标准扩展的class文件、类路径中发现的类库中的class文件等。Java虚拟机开始运行时,在应用程序启动前,至少创建一个自定义类加载器,也可能创建多个。所有这些类加载器被连接在“双亲”--“孩子”模型中,这条链的丁页端是BootstrapClassLoader,末端是CustomClassLoader。
Java动态类加载的实现
3.1类加载的过程
JVM解释字节码,必须通过加载、连接和初始化3个步骤'加载就是把符合.class格式的二进制文件读入JVM,根据所加载文件的二进制创建class对象。连接是把己经加载的二进制数据合并到虚拟机的运行时状态中去,包括验证、准备和解析3个子阶段。初始化则将类变量赋以适当的初始值。通过以上3个步骤就创建了一个类型,该类型被Java程序所使用。
Java中的类加载是动态的,整个过程是按照“双亲”委托机制来实现的_。例如,程序创建了一个名为“Grandma”的自定义类加载器。向其传递一个null的构造方法,Grandma的双亲就是BootstrapClassLoader。若程序又创建一个“Mom”的类加载器,“双亲”设为Grandma。然后又创建“Tom”,将Tom“双亲”设为Mom。现Tom加载一个名为Set的类,Tom首先找Mom来加载类型;Mom则首先去找Grandma来加载类型;而Grandma首先找BootstrapClassLoader加载,因Set是核心类,Java虚拟机在启动时己加载了这个类,所以直接返回代表Set的Class实例给Grandma,Grandma传递实例给Mom,Mom再传回给Tom,Tom返回给程序。若Tom要加载的是一个用户定义的类s^Tom委派给Mom,Mom委派给Grandma,Grandma委派给BootstrapClassLoader。BootstrapClassLoader无法加载这个类型,由Grandma尝试加载此类型,若加载成功,则返回给Mom类MyClass的Class实例,然后Mom再给Tom,Tom返回给程序。若Grandma无法加载,则由返回给Mom,Mom尝试加载,Mom可能加载成功,也可能加载不成功。若成功,则把返回的Class实例按照Grandma返回过程,返回给程序。若Mom加载不成功,Tom尝试加载,Tom若加载成功,则直接将Class实例返回给程序。否则抛出异常ClassNotFoundException。
3.2动态类加载的实现
Java动态类加载,包括运行时决定所使用的类型,加载并使用它们。有两种方法可以实现动态加载,一是通过传递类型的名字到的forName()方法,二是调用自定义类加载器的loadClass()方法。自定义类加载器可以从oader的任何子类创建。两种方法都可以使运行中的程序去调用在源代码中未提及、而在程序运行中所用的类型。动态加载支持Java的Web浏览器,它可以跨网络加载applet的class文件。当浏览器启动的时候,它不知道将从网络上加载哪个class文件,当它遇到包含这些applet的网页时候才知道每个applet所需要类型的名字。
任何类的加载都是通过抽象类ClassLoadr类及其子类来实现的,它是Java核心API的一部分,属于包,负责在运行时查找和加载类文件,包括以下5个主要方法:
Class loadClass(String name,boolean resolve)使用指定二进 制名称加载类,name指定以包表示法表示的二进制类的名称。
Class defineClass (String name, byte [] b,int off, int len)将 byte数组转换为Class类的实例,并分析Class。
findClass (String name)使用指定二进制名称查找类。 name是所找类的二进制名称。
Class findLoadedClass(String name)若 JVM 给定二进制 名称的类,则返回该类。否则,返回null。
Class findSystemClass(String name)查找具有指定 二进制 名称的类,必要时加载它。
动态类加载最直接方式是使用的forName() 方法,它有两种重载形式:
public static Class forName(String className) throws class- NotFoundException
public static Class forName(String className,boolean initia- lize,ClassLoader loader)throws ClassNotFoundException
forName ()的三参数形式以String类型的className作类 型的全限定名。若boolean类型initialize为true,类型会在for- Name()方法返回之前连接并初始化;若initialize为false,类型 会被加载,可能会被连接但不会被forName()方法明确初始化。 当类型在调用forName()前己被初始化,即使将false作为第二 个参数传到forName(),返回的类型也己经被告初始化了。第 三个参数loader传递用户定制的类加载器的引用给forName(), 让其使用这个类加载器加载类型。也可将null传递给loader, 这时调用启动类加载器。forName还可以只带一个参数,它总是 使用当前的类加载器,并初始化类型。两个版本的forName()方 法都返回Class实例引用,代表被加载的类型。若类型无法加 载,抛出 ClassNotFoundException 异常。
动态类加载的另一种方式是使用自定义类加载器的load- Class()方法。若需要用自定义的类加载器请求类型,只需要调 用自定义类加载器的loadClass ()方法。类ClassLoader包含两 个名为loadClass()的重载方法,形式如下:
protected Class loadClass (String name) throws ClassNot- FoundException
protected Class loadClass(string name, booleanresolve)throws ClassNotFoundException
两个loadClass()方法都可加载类型全限定名为String类型的name参数。若loadClass()方法使用String类型的name参数来传递全限定名,它返回被加载类型的Class实例。否则,会试图用某种用户定制的方式来加载请求的类型。若类加载器用定制的方式成功加载了一个类型,oadClass()应返回一个Class的实例,表示新加载的类型。否则,方法抛出Class-NotFoundExeption异常。
oader提供默认loadclass()实现,但仍可被子类覆盖。oader类中的loadClass()通过以下步
骤实现工作。
查看请求的类型是否己被类加载器加载(通过findLoadedClass()方法)。若己载入,返回这个己加载类型的Class实例。
否则,委托此类加载器的“双亲”加载。若返回Class实例,把这个Class实例返回。
⑶否则,调用findClass(),寻找或生成一个符合Javaclass文件格式字节数组。若成功,findClass()把字节传给defineClass(),defineClass()导入这个类型,返回Class实例。若findClass()返回了Class实例,loadClass()就把Class实例返回。
(4)否则,,mdClass()抛出异常中止处理,且loadClass()也抛出同样的异常并中止。
通常生成ClassLoader子类并实现findClass()方法的方式来实现自定义类加载。findClass()是loadClass()方法工作方式的子集。基本工作方式是:
(1)findClass()接受要类型的全限定名作为惟一参数。ftnd-Class()首先试图查找或生成内容是Javaclass文件格式字节数组。
若findClass()无法确定或生成字节数组,抛出ClassNot-FoundExeption异常。否贝丨J,findClass()调用defineClass(),把所需要的类型名字、字节数组和一个可选的指定了类型所属的保护域ProtectionDomain对象作为参数。
若defineClass()返回代表这个类型的Class实例,find-Class()把同Class实例返回给它的调用者。否则,defineClass()抛出异常并中止,,mdClass()也抛出同样的异常并中止。
比较findClass()和loadClass(),findClass()把loadClass()两个部分分离开[15],即给定一个类型的全限定名,用定制化方法查找或生成一个字节数组和定制方式决定类型的保护域,这两个部分都可以定制。findClass()实现需要执行这两个任务,结果返回一字节数组和一个ProtectionDomain对象的引用。
实现动态类加载,使用forName()还是调用用户自定义类加载器的loadClass()方法取决于用户的需要。若没有特别要求,应使用forName(),因为forName()是动态类加载最直接的方法。另外,若需要请求的类型在加载时就初始化,则不得不使用forName()方法。当loadClass()返回类型的时候,类型可能没被连接。然而,自定义类加载器可以满足一些forName()无法满足的需求。如需要一些特定的加载类型的方法,比如从网络上下载,从数据库中获取,从加密文件中提取,甚至动态地创建它们,这时就需要自定义类加载器。创建自定义类加载器,主要原因是可以用定制的方式把类型的全限定名转换成一个Javaclass文件格式的字节数组,并使用自定义类加载器而不是forName()方法对代码安全性进行保护。
Java动态类加载机制应用
本节结合网络教学系统中的一个实际应用一一基于C/S结
构的学生实验教学系统(用Java实现)来具体讨论Java动态类加载机制的应用,其中实现一个了自定类加载器(MyClass-Loader),全面展示动态类加载器的程序实现及实际应用。
学生实验教学系统开发目的是实现对Java实验程序代码的自动化管理,其中两点需求:①学生登录系统后,可查看实验内容,提交程序源码,得到反馈(通过Java编译器编译,返回是否成功编译提示);②老师登录系统后,查看学生完成情况,若完成,查看类,属性,方法,源码,测试方法。
由此可知,这里通过编译器编译及查看属性和方法,测试方法都要用到动态类加载。因当前JVM中正在运行的是学生实验教学系统。对于一个学生提交实验源程序(Java源程序)并通过编译是系统预先并不知道的类型。而老师,在运行学生实验教学系统时,要查看属性,方法并测试得先从数据库中下载程序源码(本系统中学生的源代码通过编译后,将其源代码保存在数据库中,之所以不是保存的.class文件,是因为老师也要查看源代码),并在本地编译,才可以测试,而且还要应用Java的反射机制才能满足要求。通过以上分析,完全是Java动态类加载机制的运用场景,通过自定义类加载器来实现其功能需求。图3是学生实验教学系统主界面。
根据需求,系统中自定义了一个类加载器(MyClassLoa-der),程序实现源代码如下:
//MyClassLoader继承自ClassLoader
publicclassMyClassLoaderextendsClassLoader{
publicMyClassLoader(){}
publicMyClassLoader(StringmyClasspath){…}
//覆盖单参数loadClass,调用带两参数loadClasspublicClassloadClass(StringclassName)throwsClas-sNotFoundException{
returnloadClass(className,false);}
//覆盖了父类的该方法
protectedClassloadClass(Stringname,booleanresolve)throwsClassNotFoundException
{try{ClassfoundClass=null;
//若类己在系统缓冲中,我们不必再次装入它foundClass=findLoadedClass(name);
//检查类是否加载,加载则返回类的Class实例if(foundClass!=null){returnfoundClass;}
//若加载的是系统类,用系统类加载器加载if(With("java.")){foundClass=findSystemClass(name);
returnfoundClass;}
//若不是系统类则调用findClass()方法try{foundClass=findClass(name);
}catch(Exceptionfnfe){…}if(resolve&&(foundClass!=null)){resolveClass(foundClass);}returnfoundClass;
}catch(Exceptione){…}}
//通过指定的二进制文件名来加载类文件publicClassloadClass(byte[]classData,StringclassName)throwsClassNotFoundException{
//调用defineclass方法加载指定类型Classc=defineClass(className,classData,0,);returnc;}
//定义findClass()加载的类的具体实现publicClassfindClass(StringclassName){byte[]classData=null;try{
//由class文件名找类class文件,返回字节数组.classData=loadClassData(className);
}catch(IOExceptione){…}if(classData==null){returnnull;}
//调用defineClass()方法实现类的加载Classc=defineClass(className,classData,0,);
(className,c);returnc;}
//通过指定二进制文件名加载javaclass文件privatebyte[]loadClassData(StringclassName)throwsIOExce-ption{
//searchFile寻找文件路径,用string返回.
StringfilePath=searchFile(myClasspath,className+".class");if(!(filePath==null||filePath=="")){
//读取指定javaclass文件并存储在字节数组中…}else{
//只有.java文件,调用java编译器编译后再加载filePath=searchFile(myClasspath,className+".java");if(!(filePath==null||filePath=="")){time().exec("javac"+filePath);
try{…}catch(InterruptedExceptione){…}
}else{returnnull;}}}
//searchFile查找指定二进制名称的文件
publicStringsearchFile(Stringclasspath,StringfileName)
{…}
应用自定义类加载器,当从数据库中下载Java代码后,实例化一个MyClassLoader,根据MyClassloader内部实现机制调用相关的loadClass方法,来实例化一个类,并返回这个类的Class实例,然后结合反射机制解析出相关的属性和方法等。以下这段代码是在显示类信息Classlnfo类中的实现代码:publicShowClassInfo(StringclassPath,StringfileName){try{
//classPath是上传文件路径或下载的类所在路径MyClassLoadermc=newMyClassLoader(classPath);
//指定二进制名称的文件加载入自定义类加载器c=ass(fileName);}
catch(ClassNotFoundExceptione){…}
//利用java反射机制解析类文件中声明方法//methods,构造方法consutructor和属性fields
methods=hods();
constructor=structors();
fields=lds();}
5结束语
Java动态类加载是Java程序具有动态性的关键机制,也是JVM的一项核心技术。本文对Java类加载器的体系结构、动态类加载机制的原理、实现技术,并结合学生实验教学系统的具体应用全面的论述了Java动态类加载技术。从分析可以看出动态加载机制在程序设计领域具有重要的地位,它可以自定义加载策略和实现动态名字空间,使Java程序具有极大的灵活性。深入学习Java动态类加载机制对提高Java程序质量和效率具有重要的意义。本文对Java动态类加载机制的应用是结合Java反射机制来实现的,目的是清晰说明Java动态类加载机制,然而Java动态类加载机制的应用远远不止这些,还可以应用在从网络动态加载程序组件、字节码加密、代码安全保护等方面,Java中RMI(远程方法调用)就是Java动态类加载机制的一个典型应用,而这是一般类加载器无法实现,只有通过自定义加载策略,才能完美实现这样的需求。进一步的研宄是将Java动态类加载机制原理应用在代码安全保护及网络中RMI应用方面,使其充分发挥其Java语言动态性和安全性。
中国论文网(www.lunwen.net.cn)免费学术期刊论文发表,目录,论文查重入口,本科毕业论文怎么写,职称论文范文,论文摘要,论文文献资料,毕业论文格式,论文检测降重服务。