----------------------ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
一.类加载器简介
Java是介于编译型和解释型之间的编程语言,编译器将java源代码编译成中间层字节码文件(bytecode),由Java虚拟机(Java Virtual Machine)解释和执行之。Java体系结构可以表示如下:
由上图可知类加载器是JVM的一部分,主要作用是将字节码加载进入执行引擎,以供执行。当使用java A 的时候,java.exe被调用,从而根据%JAVA_HOME%\jre\lib\i386\jvm.cfg配置来选择激活jvm,启动之后初始化工作完成之后便产生Bootstrap Loader加载器,它由C++编写。JVM中另外两个内置类加载器是ExtClassLoader和AppClassLoader,它们定义在sun.misc.Launcher.class中,为内部类,且由Bootstrp Loader加载进入虚拟机。
每个类加载器会根据预设的url来搜索.class文件并动态加载之。Bootstrap Loader的加载路径为sun.boot.class.path,表现为CLASS_PATH环境变量;ExtClassLoader加载路径为java.ext.dirs,默认是%JAVA_HOME%\jre\lib\ext;AppClassLoader加载路径为java.class.path,由执行java.exe时的-classpath或-cp参数指定;上述三个类加载器存在下图所示的继承关系:
同时,上图还说明了类加载器在Java语言中发挥的很重要的2点作用:
1.类加载器的委派模型:假设AppClassLoader需要加载一个类,它会首先委托其父加载器ExtClassLoader来加载此类,ExtClassLoader也会递归性的委托其父加载器Bootstrap Loader来加载此类,如果Bootstrap Loader在sun.boot.class.path下找到被加载类时即加载,如果无法找到时再依次由子类加载器去加载。委派模型是针对Java安全而设计的,这也印证了Java语言的设计初衷:面向网络的编程语言。
2.由同一个类加载器所加载的类只能引用该加载器和其父加载器所加载的其他类。
Java语言的的动态连接性:经编译后的.class文件对于JVM来说是一个独立的动态链接函数库,类似于Windows 操作系统下的动态联结函数库(Dynamic Linking Library)和Unix下的共享对象(Share Object)。动态连接特性使我们可以在Java程序被编译之后还能对源文件做修改,重新编译修改过的源文件且重启jvm即可更新被加载的.class文件,在Tomcat之类的Web Container下实现的热部署(Hot Deployment)可以在不重启虚拟机的情况下更新.class文件。另,动态连接性可以显示的实现,有两种方法:1) Class clazz = Class.forName(); 2) 自定义类加载器,然后利用loadClass()加载.class; 上述两种方法可以在运行时动态加载.class文件。
二.类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1.首先当前线程的类加载器去加载线程中的第一个类。
2.如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
4.每个类加载器加载类时,又先委托给其上级类加载器。
5.当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
6.对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
有一道面试,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。把先前编写的类加入到jdk的rt.jar中,会有怎样的效果呢?不行!!!看来是不能随意将自己的class文件加入进rt.jar文件中的。
- public class ClassLoaderDemo1 {
- /**
- * 类加载器演示
- * @黑马ZWF
- */
- public static void main(String[] args) {
- //加载器:sun.misc.Launcher$AppClassLoader
- System.out.println(ClassLoaderDemo1.class.getClassLoader().getClass().getName());
- //加载器:BootStrap(loader为null的情况)
- System.out.println(System.class.getClassLoader());//
- System.out.println("----------------查看类加载器的层次结构关系-------------------");
- ClassLoader loader = ClassLoaderDemo1.class.getClassLoader();
- while(loader != null){
- System.out.println(loader.getClass().getName());
- loader = loader.getParent();
- }
- }
- }
三.编写自己的类加载器
- public class ClassLoaderDemo2 {
- /**
- * 加密方法,同时也是解密方法
- * @黑马ZWF
- */
- public String toString(){
- return "hello,itcast";
- }
- public static void main(String[] args) throws Exception {
- String srcPath = args[0];
- String destDir = args[1];
- FileInputStream fis = new FileInputStream(srcPath);
- String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
- String destPath = destDir + "\\" + destFileName;
- FileOutputStream fos = new FileOutputStream(destPath);
- cypher(fis,fos);
- fis.close();
- fos.close();
- }
- private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
- int b = -1;
- while((b=ips.read())!=-1){
- ops.write(b ^ 0xff);//如果是1就变成0,如果是0就变成1
- }
- }
- }
之后在工程里新建一个文件夹,用来保存加密后的class文件
最后就需要使用我们自己的类加载器来进行解密了.
- public class MyClassLoader extends ClassLoaderDemo2{
- public static void main(String[] args) throws Exception {
- String srcPath = args[0];
- String destDir = args[1];
- FileInputStream fis = new FileInputStream(srcPath);
- String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
- String destPath = destDir + "\\" + destFileName;
- FileOutputStream fos = new FileOutputStream(destPath);
- cypher(fis,fos);
- fis.close();
- fos.close();
- }
- private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
- int b = -1;
- while((b=ips.read())!=-1){
- ops.write(b ^ 0xff);//如果是1就变成0,如果是0就变成1
- }
- }
- private String classDir;
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
- try {
- FileInputStream fis = new FileInputStream(classFileName);
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- cypher(fis,bos);
- fis.close();
- System.out.println("aaa");
- byte[] bytes = bos.toByteArray();
- return defineClass(bytes, 0, bytes.length);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- public MyClassLoader(){
- }
- public MyClassLoader(String classDir){
- this.classDir = classDir;
- }
- }
- Class clazz = new MyClassLoader("myClass").loadClass("ClassLoaderAttachment");//此处不能在使用ClassLoaderAttachment因为一旦用了之后,系统的类加载器就会去加载,导致失败,所以该类就继承了Date类了.
- Date date = (Date)clazz.newInstance();
- System.out.println(date);