java虚拟机中可以安装多个类加载器,每个java程序至少拥有三个类加载器,分别是:引导类加载器、扩展类加载器和系统类加载器。与之相对应的,系统默认三个主要类加载器,每个加载器负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader。类加载器也是java类,因为其他是java类的类加载器本身也需要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。它是虚拟机整体中的一部分,而且通常是用C语言来实现的。
java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象,或者默认采用系统类加载器为其父级类加载器。来看下类加载器之间的父子关系和管辖范围图:
来了解下类加载器的委托机制。首先当前线程中的类加载器去加载线程中的第一个类;如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B;还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类;每个类加载器加载类时,又先委托给其上级类加载器;当所有祖宗类加载器没有加载到类,则返回到发起者类加载器,还加载不了,就抛出ClassNotFoundException,而不是再去寻找发起者类加载器的子类加载器,一则没有这样的方法,二则该找哪个子类加载器无法确定。
我们也可以编写自己的类加载器,通过这样的示例来深入理解类加载器。示例代码:
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass().getName()
);
System.out.println(//这个为BootStrap类加载器所以为null
System.class.getClassLoader()
);
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
// System.out.println(new ClassLoaderAttachment().toString());
Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
Date d1 = (Date)clazz.newInstance();//用父类获取子类对象
System.out.println(d1);
}
}
上面测试代码中用到的两个类代码如下:
package cn.itcast.day2;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
String srcPath = args[0];
String destDir = args[1];
FileInputStream fis = new FileInputStream(srcPath);
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
String destFilePath = destDir + '\\' + destFileName;
FileOutputStream fos = new FileOutputStream(destFilePath);
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);
}
}
private String classDir;
@Override//重写findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir + "\\" +name/*.substring(name.lastIndexOf('.')+1)*/ + ".class";
System.out.println(name);
FileInputStream fis;
try {
fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
bos.close();
System.out.println("aaa");
return defineClass(null ,bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader(){
}
//用构造方法传递class目录
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderAttachment extends Date {
//继承Date类以便在调用的时候用父类引用创建子类对象
public String toString(){
return "hello,itcast";
}
}
上面的例子,步骤是这样的:对不带包名的class文件进行加密,加密结果存放到另外一个目录;运行加载类的程序,能够正常加载,不过类加载器并非我们需要的;删除CLASSPATH环境下的类文件,再运行就可以了。
再来看一段代码:
package cn.itcast.itcastweb.web.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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>");
loader = loader.getParent();
}
out.close();
}
}
这是一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的类,正常发布后看到打印结果为WebAppClassLoader,把MyServlet.class 文件打jar包放到ext目录中,重启Tomcat,发现找不到HttpServlet的错误,把servlet.jar也放到ext目录中,问题解决了,打印结果是ExtClassLoader。我们可以总结出,父级加载器加载的类无法引用只能被子级类加载器加载的类。