一、类加载器初识
1、概述
类加载机制不只是使用一个单一的类加载器,每个Java程序至少有三个类加载器,它们都是系统类加载器。
用户也可以通过实现java.lang.ClassLoader实现自定义类加载器。
2、JVM自带类加载器
(1)根类加载器(Bootstrap):使用c++编写,该加载器没有父加载器。它负责加载虚拟机的核心类库(典型地,来自rt.jar包下的类库)。
根类加载器依赖于底层操作系统,属于虚拟机实现的一部分,它并没有实现java.lang.ClassLoader类。
(2)扩展类加载器(Extension):纯java代码编写,ClassLoader的子类,它的父加载器为根类加载器。
它从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre\lib\ext子目录下加载类库。
你可以将你要引进的jar文件放在该目录下,加载器不需要任何class path就可以找到它们的类库。
(3)系统类加载器或应用加载器(System):纯java代码编写,ClassLoader的子类,它的父加载器为扩展类加载器,
它从环境变量classpath或系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加 载器的默认父加载器。
3、类的加载过程
类的加载采用父类委托机制,这种机制能够更好的保障Java平台的安全性。在此委托机制中,除Java自带的根加载器外,
其余的加载器都有且只有一个父加载器,如下层次图所示,当Java程序请求(your custom)loader加载(your custom)class时,
loader首先委托自己的父加载器去加载class,若父加载器能完成,则由父加载器加载,否则由loader加载。
(这种关系并非是继承关系,而是一种包装机制)。
4、命名空间
一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。那什么是同一个类?
在Java中一个类通过认证的类全名来唯一的标识。认证的类全名包括包名和类名两部分组成。
但是一个被加载到JVM中的类则通过类的全名和加载这个类的类记载器来唯一的标识。每个类加载器都有自己的命名空间,
命名空间由该加载器和所有的父加载器所加载的类组成。在同一个命名空间不会出现类的完整名字(包括包名)相同的两个类;
在不同的命名空间有可能出现完整的名字包括包名相同的两个类。
5、运行时包
由同一个类加载器加载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,
不仅要看它们的包名是否相同,还要看定义类加载器是否相同,只有属于同一个运行时包的类才能互相访问包可见的类和类成员,
这样的限制能避免用户自定义的类冒充核心类库的类,去访问包可见成员。
6、自定义类加载器
如上所述,系统类加载器是自定义类加载器的父加载器。若想实现自定义类加载器,需实现java.lang.ClassLoader类。
7.Java源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是 JVM 的指令,而不像 C、C++经由编译器直接生成机器码
二、 自定义类加载器程序演示
/* (1 )
* 测试类,然后自定义的类加载器去 加载该类
*/
public class ClassTest implements InterfaceTest{
@Override
public void name() {
System.out.println("tao");
}
@Override
public void age() {
System.out.println("21");
}
}
/* (2)
* 要加载类的接口,加载该接口的子类时,可以用接口引用,而不需要利用反射来实现。
*/
public interface InterfaceTest {
public void name();
public void age();
}
(3) 加密处理
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ClassEncrypt {
public static void main(String[] args) throws IOException {
//要加密的字节码.class文件
String srcPath="D:\\Program Files\\MyEclipse 8.5 Workspace_UTF-8\\JavaSE\\bin\\com\\hu\\ClassLoader\\ClassTest.class";
//加密之后输出的字节码.class文件的位置
String destPath="D:\\IOstream\\ClassTest.class";
FileInputStream fis=new FileInputStream(srcPath);
FileOutputStream ofs=new FileOutputStream(destPath);
cypher(fis, ofs);//加密
fis.close();
ofs.close();
}
//简单的加密,用于测试。将所有二进制位取反,即0变成1,1变成0
private static void cypher(InputStream in,OutputStream out) throws IOException{
int b=-1;
while((b=in.read())!=-1){
out.write(b^0xff);
}
}
}
(4)自定义类加载器测试
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClaLoader extends ClassLoader{
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//需要加载的.class字节码的位置
String classPath="D:\\IOstream\\ClassTest.class";
FileInputStream fis=new FileInputStream(classPath);
ByteArrayOutputStream bos=new ByteArrayOutputStream();
cypher(fis, bos);
fis.close();
byte[] bytes=bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//相应的字节码解密类,在加载E盘根目录下的被加密过的ClassTest.class字节码的时候,进行相应的解密。
private static void cypher(InputStream in,OutputStream out) throws IOException{
int b=-1;
while((b=in.read())!=-1){
out.write(b^0xff);
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz=new MyClaLoader().loadClass("a");
//这就是我们接口的作用。如果没有接口,就需要利用反射来实现了。
InterfaceTest classTest=(InterfaceTest) clazz.newInstance();
classTest.name();
classTest.age();
}
}