Java的ClassLoader介绍.doc_第1页
Java的ClassLoader介绍.doc_第2页
Java的ClassLoader介绍.doc_第3页
Java的ClassLoader介绍.doc_第4页
Java的ClassLoader介绍.doc_第5页
已阅读5页,还剩19页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

了解JAVA classloader(1)与C或C+编写的程序不同,Java程序并不是一个可执行文件,而是由许 多独立的类文件组成,每一个文件对应于一个Java类。ClassLoader是JVM中将类装入内存的那部分。而且,Java ClassLoader就是用Java语言编写的。这意味着创建您自己的ClassLoader非常容易,不必了解JVM的微小细节。与C或C+编写的程序不同,Java程序并不是一个可执行文件,而是由许多独立的类文件组成,每一个文件对应于一个Java类。此外,这些类文件并非立即全部都装入内存,而是根据程序需要装入内存。ClassLoader是JVM中将类装入内存的那部分(无论是通过网络,还是别的方法找到类文件,然后装入内存)。而且,Java ClassLoader就是用Java语言编写的。这意味着创建您自己的ClassLoader非常容易,不必了解JVM的微小细节。为什么编写ClassLoader?如果JVM已经有一个ClassLoader,那么为什么还要编写另一个呢?问得好。缺省的ClassLoader只知道如何从本地文件系统装入类文件。不过这只适合于常规情况,即已全部编译完Java程序,并且计算机处于等待状态。但Java语言最具新意的事就是JVM可以非常容易地从那些非本地硬盘或从网络上获取类。例如,浏览者可以使用定制的ClassLoader从Web站点装入可执行内容。有许多其它方式可以获取类文件。除了简单地从本地或网络装入文件以外,可以使用定制的ClassLoader完成以下任务:在执行非置信代码之前,自动验证数字签名;使用用户提供的密码透明地解密代码;动态地创建符合用户特定需要的定制化构建类;任何您认为可以生成Java字节码的内容都可以集成到应用程序中。定制ClassLoader示例如果使用过JDK或任何基于Java浏览器中的Applet查看器,那么您差不多肯定使用过定制的ClassLoader。Sun最初发布Java语言时,其中最令人兴奋的一件事是观看这项新技术是如何执行在运行时从远程的Web服务器装入的代码。(此外,还有更令人兴 奋的事-Java技术提供了一种便于编写代码的强大语言。)更一些令人激动的是它可以执行从远程Web服务器通过HTTP连接发送过来的字节码。此项功能归功于Java语言可以安装定制ClassLoader。Applet查看器包含一个ClassLoader,它不在本地文件系统中寻找类,而是访问远程服务器上的Web站点,经过HTTP装入原始的字节码文件,并把它们转换成JVM内的类。浏览器和Applet查看器中的ClassLoaders还可以做其它事情:它们支持安全性以及使不同的Applet在不同的页面上运行而互不干扰。LukeGorrie编写的Echidna是一个开放源码包,它可以使您在单个虚拟机上运行多个Java应用程序。它使用定制的ClassLoader,通过向每个应用程序提供该类文件的自身副本,以防止应用程序互相干扰。我们的ClassLoader示例了解了ClassLoader如何工作以及如何编写ClassLoader之后,我们将创建称作 CompilingClassLoader(CCL)的Classloader。CCL为我们编译Java代码,而无需要我们干涉这个过程。它基本上就类 似于直接构建到运行时系统中的“make”程序。注:进一步了解之前,应注意在JDK版本1.2中已改进了ClassLoader系统的某些方面(即Java2平台)。本教程是按JDK版本1.0和1.1写的,但也可以在以后的版本中运行。Java2中ClassLoader的变动描述了Java版本1.2中的变动,并提供了一些详细信息,以便修改ClassLoader来利用这些变动。ClassLoader的基本目标是对类的请求提供服务。当JVM需要使用类时,它根据名称向ClassLoader请求这个类,然后 ClassLoader试图返回一个表示这个类的Class对象。通过覆盖对应于这个过程不同阶段的方法,可以创建定制的ClassLoader。在本文的其余部分,您会学习Java ClassLoader的关键方法。您将了解每一个方法的作用以及它是如何适合装入类文件这个过程的。您也会知道,创建自己的ClassLoader时,需要编写什么代码。在下文中,您将会利用这些知识来使用我们的ClassLoader示例-CompilingClassLoader。方法loadClassClassLoader.loadClass()是ClassLoader的入口点。其特征如下:Class loadClass(String name, boolean resolve);name参数指定了JVM需要的类的名称,该名称以包表示法表示,如Foo或java.lang.Object。resolve参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要解析。在Java版本1.1和以前的版本中,loadClass方法是创建定制的ClassLoader时唯一需要覆盖的方法。(Java 2中ClassLoader的变动提供了关于Java 1.2中findClass()方法的信息。)方法defineClassdefineClass方法是ClassLoader的主要诀窍。该方法接受由原始字节组成的数组并把它转换成Class对象。原始数组包含如从文件系统或网络装入的数据。defineClass管理JVM的许多复杂、神秘和倚赖于实现的方面-它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成最终的。方法findSystemClass findSystemClass方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转 换成Class对象,以将该文件转换成类。当运行Java应用程序时,这是JVM正常装入类的缺省机制。(Java 2中ClassLoader的变动提供了关于Java版本1.2这个过程变动的详细信息。)对于定制的ClassLoader,只有在尝试其它方法装入类之后,再使用findSystemClass。原因很简单:ClassLoader是 负责执行装入类的特殊步骤,不是负责所有类。例如,即使ClassLoader从远程的Web站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java库。而这些类不是我们所关心的,所以要JVM以缺省方式装入它们:从本地文件系统。这就是findSystemClass的用途。其工作流程如下:1、请求定制的ClassLoader装入类。2、检查远程Web站点,查看是否有所需要的类。3、如果有,那么好;抓取这个类,完成任务。4、如果没有,假定这个类是在基本Java库中,那么调用findSystemClass,使它从文件系统装入该类。在大多数定制ClassLoaders中,首先调用findSystemClass以节省在本地就可以装入的许多Java库类而要在远程Web站点 上查找所花的时间。然而,正如,在下一章节所看到的,直到确信能自动编译我们的应用程序代码时,才让JVM从本地文件系统装入类。方法resolveClass正如前面所提到的,可以不完全地(不带解析?)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时,可以调用resolveClass,这取决于loadClass的resolve参数的值。方法findLoadedClassfindLoadedClass充当一个缓存:当请求loadClass装入类时,它调用该方法来查看ClassLoader是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。组装让我们看一下如何组装所有方法。我们的loadClass实现示例执行以下步骤。(这里,我们没有指定生成类文件是采用了哪种技术-它可以是从Net上装入、或者从归档文件中提取、或者实时编译。无论是哪一种,那是种特殊的神奇方式,使我们获得了原始类文件字节。)CCL揭密我们的ClassLoader(CCL)的任务是确保代码被编译和更新。下面描述了它的工作方式:1、当请求一个类时,先查看它是否在磁盘的当前目录或相应的子目录。2、如果该类不存在,但源码中有,那么调用Java编译器来生成类文件。3、如果该类已存在,检查它是否比源码旧。如果是,调用Java编译器来重新生成类文件。4、如果编译失败,或者由于其它原因不能从现有的源码中生成类文件,返回ClassNotFoundException。5、如果仍然没有该类,也许它在其它库中,所以调用findSystemClass来寻找该类。6、如果还是没有,则返回ClassNotFoundException。7、否则,返回该类。8、调用findLoadedClass来查看是否存在已装入的类。9、如果没有,那么采用那种特殊的神奇方式来获取原始字节。10、如果已有原始字节,调用defineClass将它们转换成Class对象。11、如果没有原始字节,然后调用findSystemClass查看是否从本地文件系统获取类。12、如果resolve参数是true,那么调用resolveClass解析Class对象。13、如果还没有类,返回ClassNotFoundException。14、否则,将类返回给调用程序。Java编译的工作方式在深入讨论之前,应该先退一步,讨论Java编译。通常,Java编译器不只是编译您要求它编译的类。它还会编译其它类,如果这些类是您要求编译的类所需要的类。(需要编译的)CCL逐个编译应用程序中的需要编译的每一个类。但一般来说,在编译器编译完第一个类后,CCL会查找所有需要编译的类,然后编译它。为什 么?Java编译器类似于我们正在使用的规则:如果类不存在,或者与它的源码相比,它比较旧,那么它需要编译。其实,Java编译器在CCL之前的一个步 骤,它会做大部分的工作。当CCL编译它们时,会报告它正在编译哪个应用程序上的类。在大多数的情况下,CCL会在程序中的主类上调用编译器,它会做完所有要做的-编译器的单一调用已足够了。然而,有一种情形,在第一步时不会编译某些类。如果使用Class.forName方法,通过名称来装入类,Java编译器会不知道这个类时所需要的。在这种情况下,您会看到CCL再次运行Java编译器来编译这个类。在源代码中演示了这个过程。使用CompilationClassLoader要使用CCL,必须以特殊方式调用程序。不能直接运行该程序,如:% java Foo arg1 arg2; 应以下列方式运行它: % java CCLRun Foo arg1 arg2 CCLRun是一个特殊的存根程序,它创建CompilingClassLoader并用它来装入程序的主类,以确保通过 CompilingClassLoader来装入整个程序。CCLRun使用JavaReflectionAPI 来调用特定类的主方法并把参数传递给它。有关详细信息,请参阅源代码。 运行示例 源码包括了一组小类,它们演示了工作方式。主程序是Foo类,它创建类Bar的实例。类Bar创建另一个类Baz的实例,它在baz包内,这是为了展示CCL是如何处理子包里的代码。Bar也是通过名称装入的,其名称为Boo,这用来展示它也能与CCL工作。每个类都声明已被装入并运行。现在用源代码来试一下。编译CCLRun和CompilingClassLoader。确保不要编译其它类(Foo、Bar、Baz和Boo),否则将不会使用CCL,因为这些类已经编译过了。% java CCLRun Foo arg1 arg2CCL: Compiling Foo.java.foo! arg1 arg2bar! arg1 arg2baz! arg1 arg2CCL: Compiling Boo.java.Boo!请注意,首先调用编译器,Foo.java管理Bar和baz.Baz。直到Bar通过名称来装入Boo时,被调用它,这时CCL会再次调用编译器来编译它。CompilingClassLoader.java以下是CompilingClassLoader.java的源代码import java.io.*;/*其实是使用发现类的java文件之后,调用javac进行编译A CompilingClassLoader compiles your Java source on-the-fly. It checksfor nonexistent .class files, or .class files that are older than theircorresponding source code.*/public class CompilingClassLoader extends ClassLoader/ Given a filename, read the entirety of that file from disk/ and return it as a byte array.private byte getBytes( String filename ) throws IOException / Find out the length of the fileFile file = new File( filename );long len = file.length();/ Create an array thats just the right size for the files/ contentsbyte raw = new byte(int)len;/ Open the fileFileInputStream fin = new FileInputStream( file );/ Read all of it into the array; if we dont get all,/ then its an r = fin.read( raw );if (r != len)throw new IOException( Cant read all, +r+ != +len );/ Dont forget to close the file!fin.close();/ And finally return the file contents as an arrayreturn raw;/ Spawn a process to compile the java source code file/ specified in the javaFile parameter. Return a true if/ the compilation worked, false otherwise.private boolean compile( String javaFile ) throws IOException / Let the user know whats going onSystem.out.println( CCL: Compiling +javaFile+. );/ Start up the compiler/调用javac进行编译Process p = Runtime.getRuntime().exec( javac +javaFile );/ Wait for it to finish runningtry p.waitFor(); catch( InterruptedException ie ) System.out.println( ie ); / Check the return code, in case of a compilation errorint ret = p.exitValue();/ Tell whether the compilation workedreturn ret=0;/ The heart of the ClassLoader - automatically compile/ source as necessary when looking for class filespublic Class loadClass( String name, boolean resolve )throws ClassNotFoundException / Our goal is to get a Class objectClass clas = null;/ First, see if weve already dealt with this oneclas = findLoadedClass( name );/System.out.println( findLoadedClass: +clas );/ Create a pathname from the class name/ E.g. java.lang.Object = java/lang/ObjectString fileStub = name.replace( ., / );/ Build objects pointing to the source code (.java) and object/ code (.class)String javaFilename = fileStub+.java;String classFilename = fileStub+.class;File javaFile = new File( javaFilename );File classFile = new File( classFilename );/System.out.println( j +javaFile.lastModified()+ c +/ classFile.lastModified() );/ First, see if we want to try compiling. We do if (a) there/ is source code, and either (b0) there is no object code,/ or (b1) there is object code, but its older than the sourceif (javaFile.exists() &(!classFile.exists() |javaFile.lastModified() classFile.lastModified() try / Try to compile it. If this doesnt work, then/ we must declare failure. (Its not good enough to use/ and already-existing, but out-of-date, classfile)if (!compile( javaFilename ) | !classFile.exists() throw new ClassNotFoundException( Compile failed: +javaFilename ); catch( IOException ie ) / Another place where we might come to if we fail/ to compilethrow new ClassNotFoundException( ie.toString() );/ Lets try to load up the raw bytes, assuming they were/ properly compiled, or didnt need to be compiledtry / read the bytesbyte raw = getBytes( classFilename );/ try to turn them into a classclas = defineClass( name, raw, 0, raw.length ); catch( IOException ie ) / This is not a failure! If we reach here, it might/ mean that we are dealing with a class in a library,/ such as java.lang.Object/System.out.println( defineClass: +clas );/ Maybe the class is in a library - try loading/ the normal wayif (clas=null) clas = findSystemClass( name );/System.out.println( findSystemClass: +clas );/ Resolve the class, if any, but only if the resolve/ flag is set to trueif (resolve & clas != null)resolveClass( clas );/ If we still dont have a class, its an errorif (clas = null)throw new ClassNotFoundException( name );/ Otherwise, return the classreturn clas;CCRun.java 以下是CCRun.java的源代码 / $Id$import java.lang.reflect.*;/*先利用CompilingClassLoader编译生成class文件,在利用反射机制调用Main函数CCLRun executes a Java program by loading it through aCompilingClassLoader.*/public class CCLRunstatic public void main( String args ) throws Exception / The first argument is the Java program (class) the user/ wants to runString progClass = args0;/ And the arguments to that program are just/ arguments 1.n, so separate those out into/ their own arrayString progArgs = new Stringargs.length-1;System.arraycopy( args, 1, progArgs, 0, progArgs.length );/ Create a CompilingClassLoaderCompilingClassLoader ccl = new CompilingClassLoader();/ Load the main class through our CCLClass clas = ccl.loadClass( progClass );/ Use reflection to call its main() method, and to/ pass the arguments in./ Get a class representing the type of the main methods argumentClass mainArgType = (new String0).getClass() ;/ Find the standard main method in the classMethod main = clas.getMethod( main, mainArgType );/ Create a list containing the arguments - in this case,/ an array of stringsObject argsArray = progArgs ;/ Call the methodmain.invoke( null, argsArray );Foo.java 以下是Foo.java的源代码/ $Id$public class Foostatic public void main( String args ) throws Exception System.out.println( foo! +args0+ +args1 );new Bar( args0, args1 );Bar.java 以下是Bar.java的源代码 / $Id$import baz.*;public class Barpublic Bar( String a, String b ) System.out.println( bar! +a+ +b );new Baz( a, b );try Class booClass = Class.forName( Boo );Object boo = booClass.newInstance(); catch( Exception e ) e.printStackTrace();baz/Baz.java 以下是baz/Baz.java的源代码 / $Id$package baz;public class Bazpublic Baz( String a, String b ) System.out.println( baz! +a+ +b );Boo.java 以下是Boo.java的源代码 / $Id$public class Boopublic Boo() System.out.println( Boo! );jvm classLoader architecture :a, Bootstrap ClassLoader/启动类加载器主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.b, Extension ClassLoader/扩展类加载器主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作c, System ClassLoader/系统类加载器主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.b, User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.类加载器的特性:1, 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。2, 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 ” 双亲委派的加载链” 结构.这4 种ClassLoader的优先级依次从高到低,使用所谓的“双亲委派模型”。确切地说,如果一个网络类装载器被请求装载一个 java.lang.Integer,它会首先把请求发送给上一级的类路径装载器,如果返回已装载,则网络类装载器将不会装载这个 java.lang.Integer,如果上一级的类路径装载器返回未装载,它才会装载java.lang.Integer。类 似的,类路径装载器收到请求后(无论是直接请求装载还是下一级的ClassLoader上传的请求),它也会先把请求发送到上一级的标准扩展类装载器,这 样一层一层上传,于是启动类装载器优先级最高,如果它按照自己的方式找到了java.lang.Integer,则下面的ClassLoader 都不能再装载java.lang.Integer,尽管你自己写了一个java.lang.Integer,试图取代核心库的 java.lang.Integer是不可能的,因为自己写的这个类根本无法被下层的ClassLoader装载。再 说说Package权限。Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这 还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如启动类装载器装载了 java.lang.String,类路径装载器装载了我们自己写的java.lang.Test,它们不能互相访问对方具有Package权限的方法。 这样就阻止了恶意代码访问核心类的Package权限方法。如下图:Class Diagram:类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为, 它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。(具体上面已经有介绍)启动类加载实现 (其实我们不用关心这块, 但是有兴趣的, 可以研究一下 ):bootstrap classLoader 类加载原理探索自定义类加载器加载一个类的步骤 :ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:Java代码 1. /检查类是否已被装载过2. Classc=findLoadedClass(name);3. if(c=null)4. /指定类未被装载过5. try6. if(parent!=null)7. /如果父类加载器不为空,则委派给父类加载8. c=parent.loadClass(name,false);9. else10. /如果父类加载器为空,则委派给启动类加载加载11. c=findBootstrapClass0(name);12. 13. catch(ClassNotFoundExceptione)14. /启动类加载器或父类加载器抛出异常后,当前类加载器将其15. /捕获,并通过findClass方法,由自身加载16. c=findClass(name);17. 18. / 检查类是否已被装载过Class c = findLoadedClass(name);if (c = null ) / 指定类未被装载过 try if (parent != null ) / 如果父类加载器不为空, 则委派给父类加载 c = parent.loadClass(name, false ); else / 如果父类加载器为空, 则委派给启动类加载加载 c = findBootstrapClass0(name); catch (ClassNotFoundException e) / 启动类加载器或父类加载器抛出异常后, 当前类加载器将其 / 捕获, 并通过findClass方法, 由自身加载 c = findClass(name); 用Class.forName加载类Class.forName使用的是被调用者的类加载器来加载类的.这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰.即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.Java代码 1. publicstaticClassforName(StringclassName)2. throwsClassNotFoundException3. returnforName0(className,true,ClassLoader.getCallerClassLoader();4. 5. 6. /*Calledaftersecuritycheckshavebeenmade.*/7. privatestaticnativeClassforName0(Stringname,booleaninitialize,8. ClassLoaderloader)9. throwsClassNotFoundException;public static Class forName(String className) throws ClassNotFoundException return forName0(className, true , ClassLoader.getCallerClassLoader(); /* Called after security checks have been made. */private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;上图中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器线程上下文类加载器java默认的线程上下文类加载器是 系统类加载器(AppClassLoader).Java代码 1. /Nowcreatetheclassloadertousetolaunchtheapplication2. try3. loader=AppClassLoader.getAppClassLoader(extcl);4. catch(IOExceptione)5. thrownewInternalError(6. Couldnotcreateapplicationclassloader);7. 8. 9. /Alsosetthecontextclassloaderfortheprimordialthread.10. Thread.currentThread().setContextClassLoader(loader);/ Now create the class loader to use to launch the applicationtry loader = AppClassLoader.getAppClassLoader(extcl); catch (IOException e) throw new InternalError(Could not create application class loader ); / Also set the context class loader for the primordial thread.Thread.currentThread().setContextClassLoader(loader);以上代码摘自sun.misc.Launch的无参构造函数Launch()。使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.大部分java app服务器(jboss, tomcat.)也是采用contextClassLoader来处理web服务。还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.使java类加载体系显得更灵活.随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择.当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器, 导致类型转换异常(ClassCastException).自定义的类加载器实现defineClass(String name, byte b, int off, int len,ProtectionDomain protectionDomain)是java.lang.Classloader提供给开发人员, 用来自定义加载class的接口.使用该接口, 可以动态的加载class文件.例如,在jdk中, URLClassLoader是配合findClass方法来使用defineClass, 可以从网络或硬盘上加载class.而使用类加载接口, 并加上自己的实现逻辑, 还可以定制出更多的高级特性.比如,一个简单的hot swap 类加载器实现:Java代码 1. importjava.io.File;2. importjava.io.FileInputStream;3. importjava.lang.reflect.Method;4. .URL;5. .URLClassLoader;6. 7. /*8. *可以重新载入同名类的类加载器实现9. *10. 11. *放弃了双亲委派的加载链模式.12. *需要外部维护重载后的类的成员变量状态.13. *14. *authorken.wu15. *16. *2007-9-28下午01:37:4317. */18. publicclassHotSwapClassLoade

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论