1. 什么是类加载器? 加载类的工具.
2. Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader
3. 类加载器也是Java类,因为其是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap,它是嵌套在虚拟机内核里面的,虚拟机内核一启动它就存在,它是用cpp语言写的一段二进制代码。
4. Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
类加载器之间的父子关系和管辖范围图
5.所有的类加载器要组织成一个树状结构
6. 类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪儿类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类(Thread类有getContextClassLoader()方法);
如果类A中引用了或者继承了类B,Java虚拟机将使用加载类A的类装载器来加载类B;
还可以直接调用ClassLoader(自己写的类加载器).loadClass()方法来指定某个类加载器去加载某个类;
7. 每个类加载器加载类时,又先委托给其他上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不到,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法。
8. 对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar后运行结果为:sun.misc.Launcher$ExtClassLoader的原因
8. 委托机制有什么好处?集中管理,如果我们写了几个类加载器,都去加载某个类,那么内存中就有多份这个类的字节码
9. 能不能自己写一个类叫java.lang.System? 为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System类。除非自己写个类加载器。
10. 实现自己的类加载器要继承ClassLoader这个抽象类,但不能覆盖其loadClass()方法,这里面有找父类加载器的流程,爸爸返回之后再接着调用findClass(),只需要覆盖findClass()方法即可,不覆盖loadClass()是想保留委托机制,而其中的defineClass()方法是将从磁盘找到的二进制数据转换为字节码(也就是Class对象)的方法,这些概念都可以在jdk文档中找到,并且可以找到详尽的示例程序。
11. 模板方法设计模式
父类-->loadClass/findClass/得到class文件的二进制数据转换成字节码-->defineClass
子类1中的LoadClass在完成公共事件后再调用(findClass)来做自己的事情
子类2中的LoadClass在完成公共事件后再调用(findClass)来做自己的事情
自己的事件代码不一样,总体的流程在父类里面已经规定好了
只需要覆盖findClass()和defineClass()即可,相当简单,你以为多复杂,你以为啊!
public class ClassLoaderTest {
public static void main(String[] args) {
// sun.misc.Launcher$AppClassLoader
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
// null
System.out.println(System.class.getClassLoader());
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
// 理解有误,将ClassLoaderTest导出的itcast.jar放到ext目录时,怎么修改ClassLoaderTest.java的代码都
// 已经
// 没用作用了,因为加载的始终是ext目录下的ClassLoaderTest的main方法
// eclise->show view > problems
}
}
12. 编写自己的类加载器
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception {
String srcPath = args[0];
String destDir = args[1];
String destFileName = srcPath.substring(srcPath.lastIndexOf(File.separator) + 1);
String destPath = destDir + File.separator + destFileName;
System.out.println("srcPath:" + srcPath);
System.out.println("destFileName:" + destFileName);
System.out.println("destPath:" + destPath);
FileInputStream fis = new FileInputStream(srcPath);
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) {
// 这个地方加密时对取出的每一个字节进行与0xff异或操作,即将0变成1,1变成0的过程。
ops.write(b ^ 0xff);
}
}
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + File.separator + name + ".class";
System.out.println(classFileName);
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis, bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader() {
}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
}
@SuppressWarnings("serial")
public class ClassLoaderAttachment extends Date {
@Override
public String toString() {
return "hello, itcast!";
}
}
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// sun.misc.Launcher$AppClassLoader
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
// null
System.out.println(System.class.getClassLoader());
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
// 有包名的类不能调用无包名的类
// System.out.println(new ClassLoaderAttachment().toString());*
// 实验成功的关键是原来无包名classpath下的ClassLoaderAttatchment.class清除,否则它会被
// AppClassLoader先加载,错误,因父类不能加载无包名的类,如果loadClass不加包名,则父类加载不到
// ,因为没有包路径名,要想让父类先加载并且出错,则加上包名,如果此时classes下的.class文件被删除,则父类
// 加载不到才轮到自定义加载器去加载,两个地方修改下:
// ClassLoaderAttachment -> cn.itcast.day2.ClassLoaderAttachment
// name -> name.substring(name.lastIndexOf('.')+1)
// 实验过程如下:
// 1.在工程的根目录下创建itcast目录
// 2.运行MyClassLoader,运行时传入ClassLoaderAttachment.class的全路径名和itcast
// 3.运行*查看正常结果,把加密过后的类覆盖掉classpath下好的ClassLoaderAttachment.class
// 4.此时*已经不能运行了,用MyClassLoader解密后加载即可
Class clazz = new MyClassLoader("itcast").loadClass("ClassLoaderAttachment");
// 此处不能在使用ClassLoaderAttachment因为一旦用了之后,
// 系统的类加载器就会去加载,导致失败,所以该类就继承了Date类了.
Date d = (Date) clazz.newInstance();
System.out.println(d);
}
}
一个类加载器的高级问题分析
·编写一个能打印出自己的类加载器名称和当前加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassLoader。
·把MyServlet.calss文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
·父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:
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.write(loader.getClass().getName() + "<br>");
loader = loader.getParent();
}
out.write(String.valueOf(loader));
out.flush();
out.close();
}
}
要配置web.xml里的servelet和servlet-mapping标签
解决方法servlet-api.jar也放到/ext/目录下,都 由ExtClassLoader加载得了,此问题之所以出现,是因为ExtClassLoader在加载 MyServlet时,因为MyServlet继承了HttpServlet,按照类加载器的原则,HttpServlet还将由ExtClassLoader加载,但ExtClassLoader的加载目录中并没有HttpServetl类。