版权声明:可以转载,但请备注原文链接:https://shuwoom.com/?p=540

一、What(是什么?)

1、概念

       Java类加载器是Java运行时环境的一部分,负责动态加载Java类到JVM的内存空间中。每个Java类必须由某个类加载器装入到内存中。每一个类加载器都有一个父类加载器(BootStrap引导类加载器没有)。

2、 JVM中有3个默认的加载器:

(1)    BootStrap:引导类加载器。这个加载器很特殊,它不是JAVA类,因此它不需要被别人加载,它嵌套在JVM内核里,也就是说JVM启动的时候BootStrap就启动了,它是C++写的二进制代码,可以加载别的类。这也是为什么System.class.getClassLoader()结果为null的原因,因为它不是JAVA类,所以它的引用返回null。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib/rt.jar

(2)    ExtClassLoader:扩展类加载器。

(3)    AppClassLoader:根据类路径来加载java类。一般我们自定义的类都是通过这个AppClassLoader加载。

3、类加载器及其委托机制

(1)  当Java虚拟机加载一个类时,如何加载呢?

首先当前线程的类加载器去加载线程中的第一个类(如类A):

l  如果类A引用了类B,Java虚拟机将加载类A的加载器去加载类B

l  还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类

(2)  委托机制的作用—防止内存中出现多分同样的字节码

例如类A和类B都要加载System类:

l  如果不用委托机制,都是自己加载,那么类A会加载一份Sysem字节码,同时类B也会加载一份字节码看,这样内存中就出现了两份System字节码。

l  如果使用委托机制,会递归地向父类查找,首选用BootStrap尝试加载,如果找不到就向下。这里System就能在BootStrap中找到然后加载。如果此时B也加载System,也从BootStrap,此时BootStrap发现已经加载过System字节码,则直接返回内存中的System字节码而不是重新加载,这样就保证了内存中只有一份字节码。

例如:用户使用一个自定义的类(没有使用自定义类加载器),那么系统就开始从AppClassLoader向父类加载器发送请求,一直到BootStrap,然后BootStrap类加载器没有父类,于是就开始查找对应路径下是否有符合要求的类。如果没有,则又向下查询,最终回到AppClassLoader(请求的发起者),如果有则返回,没有则会抛出ClassNotFoundException的异常。如果在AppClassLoader之前,其他类加载器已经找到,则由对应的类加载其返回。

代码实例1:

 

public class ClassLoaderTest {

	public static void main(String[] args)   {
		System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
        System.out.println("=========================================");
		ClassLoader loader = ClassLoaderTest.class.getClassLoader();
		while(loader != null) {
			System.out.println(loader.getClass().getName());
			loader = loader.getParent();
		}
        System.out.println(loader);
	}
}

 

代码实例2

首先我们自定义一个类:

public class Secret{

   public String key(){
       return "The key is 5132561";
   }

}

然后打印加载这个类的类加载器:

System.out.println(new Secret().getClass().getClassLoader().getClass().getName());

输出结果如下:

因为从BootStrapèExtClassLoaderèAppClassLoader这个过程中,只有到AppClassLoader才找到对应的类,所以打印AppClassLoader。

代码实例3

如果我们将Secret的字节码打成jar包并放到ExtClassLoader所指向的目录–<JAVA_HOME>/jre/lib/ext目录下,那么BootStrapèExtClassLoader,到ExtClassLoader就找到了对应的类并返回,这时就打印ExtClassLoader。

打包成jar包并保存到<JAVA_HOME>/jre/lib/ext目录下。

再次输出:

System.out.println(new Secret().getClass().getClassLoader().getClass().getName());

二、How(如何自定义类加载器?)

自定义的类加载器必须继承ClassLoader,并实现重载findClass方法。
代码实例:

public class DecodeClassLoader extends ClassLoader{
	private String classDir;

	public DecodeClassLoader(){}
	
	public DecodeClassLoader(String classDir){
		this.classDir = classDir;
	}

	@Override
	protected Class<?> findClass(String name) {
		File f = new File(classDir,name.substring(name.lastIndexOf(".") + 1) + ".class");
		try {
			InputStream in = new FileInputStream(f);
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			encode(in, out);
			byte[] bytes = out.toByteArray();
			in.close();
			out.close();
			return defineClass(bytes, 0, bytes.length);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
		return  null;
	}
	
	private static void encode(InputStream in, OutputStream out) throws IOException{
		int b = 0;
		while((b=in.read())!= -1){
			out.write(b ^ 0xff);
		}
	}	
}

三、Where(在什么地方使用?)

(1)运行时装载或卸载类。常用于:

  • 实现脚本语言
  • 用于bean生成器
  • 允许用户定义的扩展性
  • 允许命名控件之间的通信。

(2)改变Java字节码的装入,例如Java类字节码的加密

(3)修改以装入的字节码

四、类加载器的综合应用实例

通过一个加密类对一个重要的类字节码进行加密,使得只有使用解密类加载器才可以成功加载并使用。示意图如下:

(1)DecodeClassLoader.java见上面。
(2)加密类:对Secret字节码进行加密

public class EncodeUtil {
	private static void encode(InputStream in, OutputStream out) throws IOException{
		int b = 0;
		while((b=in.read())!= -1){
			out.write(b ^ 0xff);
		}
	}
	
	public static void main(String[] args) throws IOException {
		String srcPath = "E:\\java_workspace\\004ClassLoaderDemo\\bin\\com\\shuwoom\\classloader\\Secret.class";//args[0];
		String destDir = "shuwoomlib";
		
		FileInputStream in = new FileInputStream(srcPath);
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\") + 1);
		String destPath = destDir + "\\" + destFileName;
		System.out.println(destPath);
		FileOutputStream out = new FileOutputStream(destPath);
		encode(in,out);
		in.close();
		out.close();
	}
}

(3)被加载的类:

public class Secret{

	public String key(){
		return "The key is 5132561";
	}
}

首先运行EncodeUtil加密工具类,将Secret.class文件加密并保存到指定的shuwoomlib目录下,此时,在bin/bom/shuwoom/classloader目录下的Secret是未经加密的字节码。现在我们将shuwoomlib目录下加密的Secret.class替换掉bin/bom/shuwoom/classloader目录下的Secret.class。那么AppClassLoader找到的就是经过加密的字节码。

//如果直接使用AppClassLoader加载,会报错。

System.out.println(new Secret().key());

编译运行会报错:

//通过DecodeClassLoader类加载器获得Secret原字节码

ClassLoader classLoader = new DecodeClassLoader("shuwoomlib");
Class clazz = classLoader.loadClass("Secret");

Method getKeyMethod = clazz.getMethod("key");
System.out.println(getKeyMethod.invoke(clazz.newInstance(), null));

此时才能正常使用。

 

打赏

发表评论

电子邮件地址不会被公开。