黑马程序员:ClassLoader介绍、自定义ClasLoader的应用及模板方法设计模式

来源:互联网 发布:js删除class 编辑:程序博客网 时间:2024/06/15 03:50

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类加载器负责加载特定位置的类:

BootStrap,ExtClassLoader,AppClassLoader

类加载器也是Java类,因为java类的类加载器本身也要被类加载器加载,显然第一个类加载器必须不是java类,这个类是BootStrap,由C++编写的

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父类加载器对象或者默认采用系统类加载器为其父类加载器

用代码演示JVM默认三个类加载器的关系:
package cn.itcast.Mr_Zhang;public class ClassLoaderDemo {public static void main(String[] args) {// TODO Auto-generated method stubClassLoader loader = ClassLoaderDemo.class.getClassLoader();while(loader!=null){System.out.println(loader.getClass().getName());//打印当前加载器名称loader = loader.getParent(); //获取当前加载器的父类加载器赋值给loader}}}

结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
因为BootStrap加载器不是Java类,所以不能被创建对象,自然就没有getClass().getName()了,即值为null

所以JVM默认三个加载器的关系从爷->父->孙:
BootStrap->ExtClassLoader->AppClassLoader

类加载的委托机制:
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1.首先当前线程的类加载器去加载线程中的第一个类( Thread类中的方法:getContextClassLoader() 获取上下文类加载器 ,也可以设置加载器:public void setContextClassLoader(ClassLoader cl))
2.如果类A中引用了类B,JVM将使用加载类A的类加载器来加载类B
3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类

每个类加载器加载类时,又先委托给其上级类加载器
当所有父类加载器都没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不会去找发起者加载器的子类加载器,因为没有调用子类加载器的方法,即使有,也可能有很多分支,不知道加载哪一个。

各个类加载器的管辖范围:
BootStrap:jre/lib/rt.jar (System类就是用该加载器)
ExtClassLoader:jre/lib/ext/*.jar
AppClassLoader:CLASSPATH指定的所有jar或class文件

dt.jar是关于运行环境的类库,主要是swing的包   \jdk1.7.0\lib\dt.jar
tools.jar是关于一些工具的类库 \jdk1.7.0\lib\tools.jar
rt.jar包含了jdk的基础类库,也就是你在java doc里面看到的所有的类的class文件 \jdk1.7.0\jre\lib\rt.jar

若把一个class文件生成jar包放到jre/lib/ext/下的话,在执行这个class的时候,执行过程如下:
假设发起加载器为AppClassLoader,该加载器会委托给父类ExtClassLoader,其加载器再委托给父类BootStrap,因为BootStrap没有父类,所以管辖范围内是否有请求被加载的类,没有,则返回到其子类ExtClassLoader加载器,该加载器发现jre/lib/ext/下有对应class的一个jar包,这会通过该加载器加载该class,加载工作就完成了。

思考题:可否自己写一个System.class
答案:通常是不可以的,因为类加载器会委托给父类加载器,而在BootStrap加载器中就会直接加载System.class类了,自己写的System.class是不会被加载的,除非使用自己写一个特殊的类加载器去加载

模板方法设计模式
父类->loadClass

子类1,子类2的流程和父类的流程是一样的,但是流程中的局部细节无法确定,就留给子类自己去实现

写类加载器的原理:
1.自定义类MyClassLoader继承ClassLoader
2.new MyClassLoader.loadClass("需要被自定义类加载器加载的类request.class")
3.根据类加载器的委托机制,自定义加载器的loadClass方法首先会调用父类的loadClass方法,若父类为null,则调用JVM虚拟机默认的类加载器
4.当所有父类的loadClass方法都找不到request.class时,MyClassLoader会通过覆盖父类的Class findClass()查找request.class文件
5.findClass()中,需要读取request.class文件,将读取到的字节流转换为字节数组(将读取到的字节写入到ByteArrayOuputStream,再通过此输出流的toByteArray方法转化为字节数组)再通过defineClass转换为Class才可以返回,(Class defineClass(byte[] b,0,b.length)
6.此时,MyClassLoader就通过loadClass方法加载findClass()返回回来的Class了(需保证父类加载器管辖范围内都没有request.class)

练习题:
加密一个class文件,通过自定义加载器加密读取
package cn.itcast.Mr_Zhang.ClassLoader;import java.util.Date;public class MyClass extends Date {@Overridepublic String toString() {// TODO Auto-generated method stubString text = "解密成功";return text;}}

package cn.itcast.Mr_Zhang.ClassLoader;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class MyClassLoader extends ClassLoader {public static void main(String[] args) {String srcPath = args[0];String destDir = args[1];String destFilename = srcPath.substring(srcPath.lastIndexOf('\\') + 1);String destPath = srcPath.substring(0, srcPath.lastIndexOf('\\') + 1)+ destDir + '\\' + destFilename;FileInputStream fips = null;FileOutputStream fops = null;try {fips = new FileInputStream(srcPath);fops = new FileOutputStream(destPath);encrypt(fips, fops);} catch (Exception e) {e.printStackTrace();} finally {if (fops != null)try {fops.close();} catch (IOException e) {e.printStackTrace();}if (fips != null)try {fips.close();} catch (IOException e) {e.printStackTrace();}}}public static void encrypt(InputStream ips, OutputStream ops)throws IOException {int b = -1;while ((b = ips.read()) != -1) {ops.write(b ^ 0xff);}}private String pathDir;MyClassLoader() {}MyClassLoader(String pathDir) {this.pathDir = pathDir;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes = null;try {String fileName = pathDir + '\\' + name + ".class";FileInputStream ips = new FileInputStream(fileName);ByteArrayOutputStream bot = new ByteArrayOutputStream();encrypt(ips, bot);bytes = bot.toByteArray();bot.close();ips.close();} catch (Exception e) {e.printStackTrace();}return defineClass(null, bytes, 0, bytes.length);}}

package cn.itcast.Mr_Zhang.ClassLoader;import java.util.Date;public class MyClassLoaderTest {public static void main(String[] args) {// TODO Auto-generated method stubtry {Class clazz = new MyClassLoader("D:\\workspace\\Mr.Zhang\\bin\\cn\\itcast\\Mr_Zhang\\ClassLoader\\encrypt").loadClass("MyClass");// 这里用Date调用是因为编译时,MyClass因被加密,不能识别MyClass,只有运行自定义构造器后才能识别,而编译时期不能被识别Date myObject = (Date) clazz.newInstance();System.out.println(myObject.toString());} catch (Exception e) {e.printStackTrace();}}}

一个类加载器的高级问题分析:
编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet(这个会继承HttpServlet),正常发布后,看到打印结果为:
WebAppClassloader
StandardClassLoader
AppClassLoader
ExtClassLoader

把MyServlet.class文件打成jar包,放到tomcat服务器配置的jre目录下的ext目录中,重启tomcat,发现找不到HttpServlet的错误,现在把包含HttpServlet.class的jar包也放到ext目录下,问题解决了,打印结果是ExtClassLoader,原因在于ExtClassLoader加载器加载了MyServlet.class,而该class中引用HttpServlet.class,导致HttpServlet.class也由ExtClassLoader加载器加载,而ext目录下又没这个class文件的jar包。
这个问题说明上面讲过的两个事项:
1.如果类A中引用了类B,JVM将使用加载类A的类加载器来加载类B
2.父类加载器加载的类无法引用只能被子类加载器加载的类(即,若父类加载器是发起者,加载了A,而A引用B的话,不会找父类加载器的子类加载器去加载B。)

关于Eclipse的注意点:

AppClassLoader的ClassPath一定要看清楚,可以查看相关类的属性。一般是在工程中的bin目录为其ClassPath目录,非bin的子目录

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net

原创粉丝点击