您的位置:首页 > 其它

类加载器

2016-01-27 19:33 381 查看
一.三大类加载

        Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器是:BootStrap、ExtClassLoard、AppClassLoard,其中BottStrap不是java类,他同虚拟器一样是跨平台的,也就是随着操作系统的不同而改变,他是第一个进行内存开辟的类加载器,就是他把其他两个类加载器加载到虚拟机当中的,而其他两个都是java类,这三个类加载器都负责加载特定位置上的类。

二.类加载器之间的关系

        类加载器之间是一个继承树的关系,如果我们需要自定义类加载器就要让他成为这个体系中的一员。下面把每个类加载器与其对应的需加载的类进行说明:


三.委托机制

      如果当前线程的类加载器去加载线程中的第一个类A,当类A引用了类B时,java虚拟机将使用类A的类加载器来加载类B,除去这种默认方法,我们还可以通过直接调用ClassLoader.loadClass()方法来指定类加载器。

      当我们要使用到一个类时,首先当前的类加载器会委托上级加载器先去加载该类,当上级收到请求后不是直接去他指定的目录下寻找该类,而是再去找上级,一直到最顶层,这时最顶层的类加载器不能再往上抛了,他就去他的指定目录下寻找该类,如果没有找到,就返回给他的下一级,以此类推,最后返回到发起者手中,如果发起者都没有加载到该类,就会报出类不存在异常:ClassNoFoundExeption。

      委托机制的好处在于当上级加载器已经把某Class文件加载到内存当中时,如果再调用该类,就无需再次加载,这就避免了多份字节码同时存在的情况发生。

四.自定义类加载器

      自定义类加载器总共需要三步

      ①自定义类加载器首先要有一个前提,就是继承ClassLoard类(抽象类),这样我们的自定义类才能成为类加载器体系中的一员

     
②然后就需要复写ClassLoard中的方法,在自定义类加载器时一般都会复写findClass(String name)方法,用于当上级加载器找不到所需类时,自己去加载该类,还有一个方法比较重要:loardClass(String
name),此方法大多数情况不用复写,他也是用来根据类名获取该类字节码,他不用复写的原因在于这个方法会使用委托机制,当上级没找到返回来之后,此方法会启用findClass方法,所以说我们要想保留委托机制,就不要去复写该方法,而只改动findClass方法就可以。

     
③使用ClassLoard中的defineClass()方法将找到的Class文件加载到内存当中转换成字节码文件。

五.练习

      编写一个程序,首先自定义一个类,然后自定义类加载器加载该类,我们希望生成的Class文件是经过加密的,而我们自定义的类加载器可以具有解密的功能。

     
首先自定义一个类,这个类需要继承一个可以被编译器加载的类,因为在后期使用时,我们不能使用这个类名,那个时候我们的自定义类加载器还没有加载该类。

//这个类就是要被我们自定义类加载器加载的类
public class ClassLoadAcce extends Date{
public String toString()
{
return "你好!自定义类加载器!";
}
}

     
自定义类加载器,在该加载器中定义加密和解密的方法,为了方便使用异或,这样该方法既可以加密又可以解密。在main函数中,我们在运行时期动态的把原Class文件的绝对路径和我们自定义类指定的目录(此处为classLoard)作为参数传递给main函数,因为Eclipse会实时编译把ClassLoadAcce的Class文件存放到bin目录下,我们利用IO流和加密方法读取bin目录下的class文件,加密后把他放到自定义目录下。复写findClass方法,加载加密后的class文件,并进行解密。

/*
* 这是我们自己定义的一个类加载器
* ①首先定义一个附件类用来产生Class文件,这个类叫做ClassLoadAcce,他生成的Class文件是放在
* bin目录下,我们自定义一个目录放在该工程下名字叫做classLoard。
*
* ②经过args(main方法参数)读取ClassLoadAcce.class的路径名然后经过IO流的加密功能把加密过的class文件放到
* 自定义的目录classLoad下面
*
* ③复写ClassLoad类的findClass方法,当委托机制找不到该class文件时,返回到发起者手中
* 也就是我们的自定义加载器这里,他就会执行findClass方法来 拯救世界了
* 	findClass方法中从指定的文件中读取出Class文件,并返回给调用者
*
* */
public class MyClassLoard extends ClassLoader{

public static void main(String[] args) throws IOException{
//从javac中接收两个参数,分别是源文件和目标目录
String source = args[0];
String destPath = args[1];

//构建文件读取流,获取该文件
FileInputStream fis = new FileInputStream(source);
System.out.println(source);

//从源文件中截取文件名
String fileName = source.substring(source.lastIndexOf("\\")+1);

//将截取好的文件名写入到当前项目中
String fileDest = destPath+File.separator+fileName;

FileOutputStream fos = new FileOutputStream(fileDest);

encrypt(fis,fos);

fis.close()
4000
;
fos.close();

}
//定义一个简单的加密算法,使用异或
private static void encrypt(InputStream is,OutputStream os)throws IOException
{
int len=-1;
while((len = is.read())!=-1)
{
//异或255进行加密
os.write(len^255);
os.flush();
}
}

//定义成员变量,用来接收指定的class路径
private String classPath;

public MyClassLoard(){}

public MyClassLoard(String classPath)
{
this.classPath = classPath;
}

//复写findClass方法让他去指定目录classLoad中寻找class类
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {

//根据使用自定义类加载器时传进来的文件目录classPath和文件名name,获取该class的全部名称
StringBuilder className = new StringBuilder(classPath);
className.append(File.separator+name+".class");
System.out.println("自定义类加载器所要获取的文件路径:"+className);
//读取该文件
try
{
FileInputStream fis = new FileInputStream(className.toString());
//把读取到的class文件写到内存中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
//对读取到文件进行解密
encrypt(fis, bao);
fis.close();
byte[] buf = bao.toByteArray();
//返回读取到的class文件
return defineClass(buf, 0, buf.length);

}catch(IOException e)
{
e.printStackTrace();
}

//否则返回委托机制找父类去
return super.findClass(name);
}

}

在该类中使用自定义类加载器加载该文件

public class ClassLoadTest {

public static void main(String[] args) throws Exception{

/*获取我们要读取的那个Class文件字节码,此处类加载器的发起者是AppClassLoader,
如果我们已经把bin目录下的class文件删掉了,此处报错,如果没删,此处读取的是bin目录下的class文件*/
//		System.out.println(new ClassLoadAcce().toString());
//		System.out.println(new ClassLoadAcce().getClass().getClassLoader());

/*使用我们自定义的类加载器,他会先使用委托机制向上找,找不到返回到发起者这里来
我们自定义findClass方法在classLoad文件夹下找到加密过得class文件并返回*/
Class cls = new MyClassLoard("classLoad").loadClass("ClassLoadAcce");

System.out.println(cls);
//此处不能直接new ClassLoadAcce对象,因为ClassLoadAcce在编译时期还没有吧他的字节码加载到内存中
Date ca= (Date)cls.newInstance();
System.out.println(ca);

}

}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: