类加载器
·简要介绍什么事类加载器和类加载器的作用
·Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader
·类加载器也是Java类,因为其是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap
·Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
类加载器之间的父子关系和管辖范围图
用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的lqqheima.jar包,再在eclipse中运行这个类,运行结果显示为ExtClassLoader。此时的环境状态时classpath目录有ClassLoaderTest.class,ext/lqqheima.jar包中也有ClassLoaderTest.class,这个时候我们就需要了解类加载的具体过程和原理了。以上情况验证的结果是打印出显示加载ClassLoaderTest.class的加载器是ExtClassLoader,而不再是之前的AppClassLoader加载器了。
类加载器的委托机制
·当Java虚拟机要加载一个类时,到底派出哪儿类加载器去加载呢?
-首先当前线程的类加载器去加载线程中的第一个类。
-如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
-还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
·每个类加载器加载类时,又先委托给其他上级类加载器。
-当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不类,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
-对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的lqqheima.jar后运行结果为:sun.misc.Launcher$ExtClassLoader
null
sun.misc.Launcher$ExtClassLoader
null
理解:
每个ClassLoader本身只能分别加载特定位置和目录的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级退回到子孙类装载器去进行真正的加载。当退回到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
有一道面试题:能不能自己写一个类加java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System类。
编写自己的类加载器
·知识讲解
-自定义的类加载器的必须继承ClassLoader
-LoadClass方法与findClass方法
-defineClass方法
·编程步骤
-编写一个对文件内容进行简单加密的程序。
-编写一个自己的类装载器,可实现对加密过的类进行装载和解密。
-编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除类使用类 ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
·实验步骤:
-对不带包名的class文件进行加密,加密结果存放在另外一个目录,例如:java MyClassLoader MyTest.class F:/itcast
-运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为:
AppClassLoader: java MyClassLoader MyTest F:/itcast
-用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
-删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
模板方法设计模式
父类-->loadClass/findClass/得到class文件的转换成字节码-->defineClass
子类1中的 LoadClass在完成公共事件后再调用(findClass)来做自己的事情
子类2中的LoadClass在完成公共事件后再调用(findClass)来做自己的事情)
代码:
package lqq.heima.day2;
import java.util.Date;
@SuppressWarnings("serial")
public class ClassLoaderAttachment extends Date {
@Override
public String toString() {
return "hello,hiema!";
}
}
package lqq.heima.day2;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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];
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 {
// TODO Auto-generated method stub
String classFileName = classDir +File.separator+name.substring(name.lastIndexOf('.')+1)+".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 (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
package lqq.heima.day2;
import java.util.Date;
public class ClassLoaderTest {
/**
* @param args
*/
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
.getName());
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(loader);
//System.out.println(new ClassLoaderAttachment().toString());
Class clazz = new MyClassLoader("lqqheimalib").loadClass("lqq.heima.day2.ClassLoaderAttachment");
Date d = (Date) clazz.newInstance();
System.out.println(d);
}
}
编写程序的注意点:
1.带包的类不可以访问不带包的类,即在包里面的类在用 new关键字new不带包的类的时候系统报告错误:NotPackageClass cannot be resolved to a type
示例代码:
public class NotPackageClass {
}
package lqq.heima.day2;
public class IncludePackageClass {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//new NotPackageClass();//NotPackageClass cannot be resolved to a type
}
}
2.子类的方法不可以抛出比父类方法中更广泛的异常,下面代码中的覆盖的方法抛出的异常是:ClassNotFoundException 不可以改成Exception。如果修改会报告错误:
Exception Exception is not compatible with throws clause in ClassLoader.findClass (String)
示例代码:
package lqq.heima.day2;
import java.io.FileInputStream;
/*
2.子类的方法不可以抛出比父类方法中更广泛的异常,下面代码中的覆盖的方法抛出的异常是:ClassNotFoundException 不可以改成
Exception。如果修改会报告错误:
Exception Exception is not compatible with throws clause in ClassLoader.findClass (String)
*/
public class ClassLoaderExceptionTest extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
//protected Class<?> findClass(String name) throws Exception
{
// TODO Auto-generated method stub
//FileInputStream in = new FileInputStream("classpath");
return super.findClass(name);
}
}
一个类加载器的高级问题分析
·编写一个能打印出自己的类加载器名称和当前加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassLoader。
·把MyServlet.calss文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
·父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:
package cn.lqqheima.lqqheimaweb.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;
@SuppressWarnings("serial")
public class MyServlet extends HttpServlet {
/**
* The doGet method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to get.
*
* @param request the request send by the client to the server
* @param response the response send by the server to the client
* @throws ServletException if an error occurred
* @throws IOException if an error occurred
*/
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.flush();
out.close();
}
}