一.类加载器及其委托机制的深入分析
<1>java虚拟机中可以安装多个类加载器,系统默认3个主要的类加载器,每个类加载器负责加载特定位置上的类:
BootStrap,ExtClassLoader,AppClassLoader
<2>类加载器也是一个java类,因此也需要类加载器来加载,显然必须有一个类加载器不是java类,这正是BootSrtap
<3>每个类加载器都应该有一个父类,实例化每一个类加载器对象时,需要为其指定一个父级类加载器对象或者是默认的采用系统类加载器为其父类类加载器。
实例代码:
package day3;
public class ClassLoaderTest {
public static void main(String[] args){
String name=ClassLoaderTest.class.getClassLoader().getClass().getName();
System.out.println(name);
//System类是由BootStrap类加载器加载的,因为它不是java类,所以如果要得到名字,或有空指针异常
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);
}
}
运行截图:
类加载器的委托机制:
当java虚拟机要加载某一个java类时,到底由哪个类加载器加载呢?
1>首先当前线程的类加载器去加载线程中的第一个类。
2>如果类A中引用类B,java虚拟机将使用加载类A的类加载器加载类B。
3>还可以直接调用ClassLoader.loaderClass()方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又先委托给其上级类加载器。
》当所有祖宗类加载器没有加载到类时,回到发起者类加载器,还加载不了,则抛出ClassNotFundException,不是再去找发起者类加载器的儿子,因为,没有getChild方法,即使有,那么该给谁呢?不能偏心吧。
》对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jar/lib/ext目录下的itcast.jar包中后,运行结果是ExtClassLoader的原因。
编写自己的类加载器
1>自定义类加载器必须继承自ClassLoader
2>loadClass方法和findClass方法。loadClass方法首先先找父类,然后再回来找findClass方法。因此只要写findClass就可以了
3>defineClass方法
模板方法设计模式:父类有个loadClass,子类的loadClass都是一样的,每个子类的区别就是findClass方法,但是每个子类的流程都是一样的。每个流程在父类中都一样,因此在父类中编写,但是也有局部细节需要子类自己实现,因此子类只要继承父类实现抽象方法findClass方法就好了。
如何把class文件的内容转换成字节码,这个由defineClass方法完成。这也是父类提供的。
练习:
编写对一个文件内容进进行简单加密程序。
运行过程:1>写好ClassLoaderAttach.java程序,再写好MyClassLoader.java程序。将ClassLoaderAttach.class路径作为源文件的参数传入,再建一个文件夹itcastlib,这个作为目标文件的路径,也作为参数传入。最后运行即可再itcastlib文件中得到ClassLoaderAttach.class的文件。然后用这个已经加密的字节码文件覆盖之前的ClassLoaderAttach.class字节码文件,然后运行。这时得不到正确的输出。
代码:
import java.util.Date;
public class ClassLoaderAttach extends Date {
public String toString(){
return "hello mmx";
}
}
package day3;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader {
public static void main(String[] args)throws Exception{
//文件路径和目的路径
String srcPath=args[0];
String destDir=args[1];
FileInputStream fis=new FileInputStream(srcPath);
//得到目的文件的绝对路径
String destFileName=srcPath.substring(srcPath.lastIndexOf('/')+1);
String destPath=destDir+"\\"+destFileName;
FileOutputStream fos=new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
private static void cypher(InputStream ins,OutputStream ops)throws Exception{
//读数据
int b=-1;
while((b=ins.read())!=-1){
ops.write(b^0xff);//进行异或进行加密
}
}
}
运行将会报错:
实例:
编写解密类加载器
步骤:让我们之前写的类继承ClassLoader,那么该类就是类加载器了。
package day3;
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 {
public static void main(String[] args)throws Exception{
//文件路径和目的路径
String srcPath=args[0];
String destDir=args[1];
FileInputStream fis=new FileInputStream(srcPath);
//得到目的文件的绝对路径
String destFileName=srcPath.substring(srcPath.lastIndexOf('/')+1);
String destPath=destDir+"\\"+destFileName;
FileOutputStream fos=new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
private static void cypher(InputStream ins,OutputStream ops)throws Exception{
//读数据
int b=-1;
while((b=ins.read())!=-1){
ops.write(b^0xff);//进行异或进行加密
}
}
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//把包名去掉了。
//如果父类加载器加载的话,就需要包名
String classFileName=classDir+"\\"+name.substring(name.lastIndexOf(".")+1)+".class";
System.out.println("aaa");
try{
FileInputStream fis=new FileInputStream(classFileName);
ByteArrayOutputStream bos=new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
bos.close();
byte[] bytes=bos.toByteArray();//得到自己数组
return defineClass(bytes,0,bytes.length);
}catch(Exception e){
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader(String classDir){
this.classDir=classDir;
}
}
package day3;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
Class clazz=new MyClassLoader("itcastlib").loadClass("ClassLoaderAttach");
//ClassLoaderAttach这个类是加密过的,因此不能直接得到实例,编译器都会出错。
Date att=(Date)clazz.newInstance();
System.out.println(att);
}
}
类加载器的一个高级问题分析
tomcat也是由javaVM加载的,当写一个MyServlet加载时,是由apache的类加载器加载的。是由,如果只是将MyServlet放到/jar/lib/ext目录下时,但是该类继承了HttpServlet,所有需要将servlet-api.jar放到该ext目录下,这样MyServlet就是由ExtClassLoader加载。