Java虚拟机可以安装多个类加载器,系统默认主要有三个类加载器,每个类负责加载特定的位置的类。 这三个类加载器是:BootStrap,ExtClassLoader,AppClassLoader。
类加载器也是一个Java类,所以类加载器也要被加载器加载,这样加载类加载器的加载器就不是一个Java类,这个加载器 就是BootStrap。它是一个嵌套在Java虚拟机内核中的加载器。
JVM的类加载器采用的是具有父子关系的树形结构进行组织的,所以在实例化每个类加载器时需要为其指定一个父级类加载器 对象,或者采用系统类加载器为其父类加载器。 BootStrap,ExtClassLoader,AppClassLoader的关系: BootStrap在这三个加载器的最上层节点,是爷爷节点,它加载jre/lib.rt.jar包中的类,比如我们常用的lang,util,io...这些包中的 类都是使用的该加载器加载的。 ExtClassLoader在第二层节点,它负责加载jre/lib/ext/.jar)下的所有jar包中的Java类。如果想让某些类使用该加载器加载,则可以 将这些类打包放在该目录下。 AppClassLoader加载器是处在上面两个父节点和爷爷节点下面的,它主要负责加载classPath指定的所有jar或目录。
使用反射查看我们自己创建的类是使用的那个类加载器加载的。
public class ClassLoaderTest {
public static void main(String[] args) {
//查看当前类使用的类加载器
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());//sun.misc.Launcher$AppClassLoader
//产看System类的类加载器
//System.out.println(System.class.getClassLoader().getClass().getName());//NullPointerException
System.out.println(System.class.getClassLoader());//null 加载它的加载器是BootStrap,这并不是一个类,所以使用上述方法会出现异常
//获取本类的所有父类
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
/*
* sun.misc.Launcher$AppClassLoader
* sun.misc.Launcher$ExtClassLoader
* null
*/
}
}
通过上述程序就可以看出,ExtClassLoader是AppClassLoader的父加载器,BootStrap又是ExtClassLoader的父加载器。
二,类加载器的委托机制Java虚拟机在加载类时到底是使用哪个类加载器去加载类呢?首先,当前线程的类加载器去加载线程中的第一个类。如果一个类中还调用了另一个类,比如类A中调用了类B,那么JVM就会调用A的加载器去加载B这个类。
委托:每个类加载器在加载一个类的时候,会先委托给上级类加载器去加载,当所有祖宗类加载器没有加载到该类时,就会返回给孩子加载器加载,如果还没有加载到,最终会返回给发起者,如果发起者还没有加载到就会抛出ClassNotFoundException。不会再去找发起者的孩子加载器去加载了。
面试:可不可以自己定义一个System类。通常是不可以的,因为在加载自定义System类时,类加载器的委托机制会委托上级类加载器来加载这个类,然后BootStrap类加载器会在rt.jar中找到这个System类,永远不会加载自己定义的那个System类。当然也有办法让类加载器加载自定义的这个System类,那就是自定义一个类加载器。
三,模板方法设计模式模板设计模式:一个类的所有子类都要按照某个流程实现某些功能,将流程代码放在父类中,然后子类继承父类就继承了父类中的那个流程,流程中的某些细节需要子类自己去实现,所以所有子类的实现细节不一样。这种设计模式就是模板方法设计模式。典型的模板设计模式就是类加载器,因为任意一个类加载器都是具有委托机制的,子类在加载某个类时,必须先委托给自己的父类加载器去加载这个类,如果父类没有加载到这个类,就会返回该发起者加载,这种委托父类加载器加载类的流程是所有子类必须实现的,所以将这个流程放在父类中,当父类没有加载到时,返回给发起者,每个发起者加载类的方法不一样,所以在每个子类中重写各自的加载方法。ClassLoader这个类中的流程就是loadClass()方法,我们在继承该类时不需要重写该方法,只需要重写findClass()方法。
自定义类加载器就是用到了模板设计模式。
四,自定义类加载器首先自定义一个目标类,将该类作为要解密的对象,备用。
public class MyClassLoader extends ClassLoader {
//加密一个类文件
public static void main(String[] args) throws Exception {
String srcFile = args[0];
String destdir = args[1];
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new
FileOutputStream(destdir + "\\" + srcFile.substring(srcFile.lastIndexOf("\\") + 1));
cyphe(fis,fos);
fis.close();
fos.close();
}
//加密解密方法,因为一个数异或两次还是这个数,所以这个方法既是加密方法,也是解密方法
private static void cyphe(InputStream fis, OutputStream fos) throws Exception {
int b = -1;
while((b = fis.read()) != -1) {
fos.write(b ^ 0xff);
}
}
//自定义类加载器
private String classDir;
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
FileInputStream fis = null;
String fileName = classDir + "\\" + name + ".class";
try {
fis = new FileInputStream(fileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cyphe(fis,bos);
byte[] bytes = bos.toByteArray();
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
//使用自定义的类加载器加载加密后的字节码文件,创建实例对象
public class Test {
public static void main(String[] args) throws Exception {
Class cls = new MyClassLoader("yue").loadClass("ClassLoaderAttachment");
Date d = (Date)cls.newInstance();
System.out.println(d);
}
}
该方法可以将指定目录中的一个字节码文件加密后放入指定目录,使用该类加载器创建加密字节码的实例对象时,只有使用该加载器才能读取加密后的文件。
五,类加载器的一个高级问题我们新建一个web应用程序的时候,可以通过下面的方法获取到Tomcat服务器的类加载器:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ClassLoader loader = this.getClass().getClassLoader();
while(loader!=null) {
out.println(loader.getClass().getName() + "<br>");
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
out.close();
}
}
结果:org.apache.catalina.loader.WebappClassLoaderorg.apache.catalina.loader.StandardClassLoadersun.misc.Launcher$AppClassLoadersun.misc.Launcher$ExtClassLoader所以Tomcat服务器的类加载器父子关系就是这样的。
一个小问题:当MyEclipse设置了断点时,启动Tomcat服务器会出现source找不到的错误。