---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
一、类加载器概述
1、类加载器是一种特殊的组件,负责在运行时查找和装入类文件。
2、java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader。
3、类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须要第一个类加载器不是java类,这正是BootStrap。
4、java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织。在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器。
例1:
package com.cn.itcast;
public class ClassLoaderTest {
public static void main(String[] args) {
//放置在不同位置的类由不同的类加载器加载
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass().getName());
//System.out.println(System.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);
}
}
输出结果:
sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
二、类加载器之间的父子关系与管辖范围

由例1可知,ClassLoaderTest类是由AppClassLoader类加载器加载的。现在,我们利用MyEclipse的打包工具,将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,此时再次运行ClassLoaderTest,输出结果:
sun.misc.Launcher$ExtClassLoader
null
sun.misc.Launcher$ExtClassLoader
null
此时的环境状态是,classpath的目录中有ClassLoaderTest.class,而jre/lib/ext/itcast.jar包中也有ClassLoaderTest.class。输出结果表明,由ExtClassLoader加载了该类。这时候,我们需要继续了解类加载的具体过程与原理。
三、类加载器的委托机制
1、当java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)其次,如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B。
3)另外,可以自定义类加载器并调用loadClass()方法来加载某个类。
2、每个类加载器加载类时,又先委托给其上级类加载器
每个类加载器本身只能分别加载特定位置和目录中的类,但它们可以委托给其他的类加载器去加载类,这就是类加载器的委托机制。
类加载器一级级地委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,回退到它的子类加载器。以此类推,就这样一级级地回退到子孙类加载器。当回退到最初的类加载器时,如果它自己也加载不了,则抛出ClassNotFoundException。注意:发起者类加载器不会再委托给它的子级类加载器。
四、自定义类加载器
1、自定义的类加载器必须继承ClassLoader类。
2、通过重写ClassLoader类中的几个重要方法来实现自定义,如:
1)findClass(String name):根据二进制类文件名来查找类
2)loadClass(String name,boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类
一般来说,推荐重写第一个方法。
JDK1.7中提供了自定义类加载器的范例:
class NetworkClassLoader extends ClassLoader { //继承ClassLoader类
String host;
int port;
public Class findClass(String name) { //重写findClass方法
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) { //这里自定义了loadClassData方法
// load the class data from the connection
. . .
}
}
例2:自定义类加载器
思路:
1、编写一个对文件内容进行简单加密的程序。
2、编写一个自己的类加载器,可实现对加密过的类进行装载和解密。
3、编写一个程序调用类加载器加载类。注意,在源程序中不能使用该类名定义引用变量,因为编译器无法识别之歌类。
①首选,让系统类加载器去加载未被加密的类
/*
* 被加密、解密及被自定义类加载器加载的类
*/
package Com.cn.ItCast;
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello,itcast";
}
}
/*
*测试类,调用类加载器去加载类
*/
package Com.cn.ItCast;
public class ClassLoaderTest {
public static void main(String[] args) {
//首先让类加载器加载未被加密的ClassLoaderAttachment类
System.out.println(new ClassLoaderAttachment().toString());
}
}
输出结果:
hello,itcast
②让系统类加载器去加载被加密了的类
首先,对被加密文件进行加密:
/*
* 自定义类加载器。同时也负责对类文件进行加密、解密
*/
package Com.cn.ItCast;
import java.io.ByteArrayOutputStream;
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{
String classDir;
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir=classDir;
}
public static void main(String[] args) throws Exception{
String srcPath=args[0]; //被加密类Class文件的地址
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 is,OutputStream os)throws Exception {
int b=-1;
while((b=is.read())!=-1){
os.write(b^0xff);
}
}
//重写findClass方法
@Override
@SuppressWarnings("deprecation")
protected Class<?> findClass(String name){
String classFileName=classDir+"\\"+name+".class";//获取加密后的类文件地址
try {
FileInputStream fis=new FileInputStream(classFileName);
ByteArrayOutputStream bos=new ByteArrayOutputStream();
cypher(fis,bos);
byte[] bytes=bos.toByteArray();
return defineClass(bytes,0,bytes.length);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
运行时传递参数:
其次,将加密后的类文件替换classpath下未加密的同名类文件,并再次运行ClassLoaderTest程序。
输出结果:
ClassFormatError: Incompatible magic value 889275713 in class file Com/cn/ItCast/ClassLoaderAttachment
③让自定义类加载器加载被加密的类文件
/*
*测试类,调用类加载器去加载类
*/
package Com.cn.ItCast;
public class ClassLoaderTest {
public static void main(String[] args) {
//让自定义类加载器加载加密后的类文件
/*让自定义类加载itcastlib/ClassLoaderAttachment.class,此时虽然有委托机制,
但系统类加载器在bin目录下未找到ClassLoaderAttachment.class文件,故由自定义类加载器加载。
如果在bin目录下拷贝一份加密了的ClassLoaderAttachment.class,则会先被系统类加载器加载,从而出错*/
Class clazz=new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
//不能用ClassLoaderAttachment名字创建实例对象,否则会让编译器自动调用系统类加载器,从而出错
Date d=(Date)clazz.newInstance();
System.out.println(d);
}
}
输出结果:
五、用类加载器管理资源和配置文件
1、获取配置文件的第一种方法:
InputStream ips = new FileInputStream("config.properties");
Properties props = new Properties();
props.load(ips);
ips.close();
注意:此方法要求必须用绝对路径。但完整的路径不是硬编码写死的,而是通过计算得出的,例如,调用getRealPah方法得到。
2、获取配置文件的第二中方法:
类加载器可以加载.class文件,也可以加载普通文件。
InputStream ips = ReflectTest.class.getClassLoader().getResourceAsStream("cn/itcast/config.properties");
通过类的.class对象获得类加载器,然后用类加载器加载配置文件。
注意:
配置文件必须放在classpath目录下,这样类加载器才能找到文件。
此方法中需要写上包名,默认从classpath的根目录下开始查找。
3、获得配置文件的第三种方法:
InputStream ips = ReflectTest.class.getResourceAsStream(“config.properties”);
Class对象本身也自带一种加载资源文件的方法,其实该方法内部调用了getClassLoader()方法。
注意:此方法中只需要写配置文件的名字,不需要写上包名。因为默认在类自己所在包下面找。
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------