2.classloader 类加载器详解
2.1 简介
1.Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
2.类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类
加载器不是不是java类,这正是BootStrap。
3.Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,
需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
2.2简单测试
1. 逐一编写如下代码来说明放置在不同位置的类确实由不同的类加载器加载的:public static void main(String[] args) {
System.out.println("AppClassLoader : "+LoaderTest1.class.getClassLoader());
System.out.println("用户自定义的jar包 放在ext目录下加载的jar : "+TestExtLoader.class.getClassLoader().getClass().getName());
System.out.println("由jvm初始加载器加载的jar 包 BootStrap: "+String.class.getClassLoader());
}。
2.用下面的代码让查看类加载器的层次结构关系
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//打印出当前的类装载器,及该类装载器的各级父类装载器
while(loader != null)
{
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
2.3类加载器之间的父子关系和管辖范围图
BootStrap ----- JRE/lib/rt.jar 顶层加载器
ExtClassLoade--- JRE/lib/ext/*.jarAppClassLoader-- CLASSPATH指定的所有jar或目录
MyClassLoader--- 用户自定义加载器 一般加载用户自己加过密的class类
注意事项:
用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,再在eclipse中
运行这个类,运行结果显示为ExtClassLoadr。此时的环境状态是classpath目录有ClassLoaderTest.class,
ext/itcast.jar包中也有ClassLoaderTest.class,这时候我们就需要了解类加载的具体过程和原理了。
2.4 类加载器的委托机制
1.当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
2.每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
2.5 编写字节的类加载器
1.自定义加载器编写原理:自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法
2.编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别
这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器
或者系统类加载器,然后再使用Class.forName。
3.实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
package classLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
public class MyClassLoader extends ClassLoader{
private String dir = null;
public MyClassLoader(String dir) throws RuntimeException{
if(new File(dir).isDirectory()){
this.dir = dir;
}else{
throw new RuntimeException("The dir : '"+dir+"' is not find !");
}
}
public static void main(String[] args) throws Exception {
MyClassLoader m = new MyClassLoader("idcastlib");
Date d = (Date)m.loadClass("TestLoader").newInstance();
System.out.println(d.toString());
}
@SuppressWarnings("deprecation")
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String filePath = name+".class";
File f = new File( dir , filePath);
System.out.println(f.getPath());
FileInputStream fi = null;
ByteArrayOutputStream bo = null;
try {
fi = new FileInputStream(f);
bo = new ByteArrayOutputStream();
encrypt(fi,bo);
byte[] bs = bo.toByteArray();
System.out.println(bs.length);
return this.defineClass( bs, 0, bs.length);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
throw new ClassNotFoundException(name + " not find ");
} catch (IOException e) {
// TODO Auto-generated catch block
System.err.println("io error");
} finally{
try {
if(fi != null)fi.close();
if(bo!=null)bo.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return super.findClass(name);
}
/**
* 对class文件进行加密处理
* @param is
* @param out
* @throws IOException
*/
private void encrypt(InputStream is, OutputStream out) throws IOException {
int i = -1;
while((i = is.read())!= -1){
out.write(i^0xff);
}
}
}
//类加载器不能加载这种非public的类
/*
Exception in thread "main" java.lang.IllegalAccessException: Class MyClassLoader
can not access a member of class MyTest with modifiers ""
*/
/*
class MyTest
{
public void test()
{
System.out.println("hello,www.it315.org");
}
}
2.6 一个类加载器的高级问题分析
问题步骤:1.编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,
看到打印结果为WebAppClassloader。
2.把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
3.把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
结论: 父级类加载器加载的类无法引用只能被子级类加载器加载的类。