【Java类加载机制】深入类加载器(二)自定义加密、解密类加载器

类加载器原理

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。


类加载器树状结构、双亲委托(代理)机制

1、引导类加载器(C语言编写)

2、扩展类加载器(Java编写)

3、应用程序类加载器(Java编写)

4、自定义类加载器(Java编写)
在这里插入图片描述
来测试一下ClassLoader的层级关系

package cn.hanquan.classloader;

public class Loader {
	public static void main(String[] args) {
		System.out.println(ClassLoader.getSystemClassLoader());
		System.out.println(ClassLoader.getSystemClassLoader().getParent());
		System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
	}
}
jdk.internal.loader.ClassLoaders$AppClassLoader@28c97a5
jdk.internal.loader.ClassLoaders$PlatformClassLoader@512ddf17
null

获取目前的系统类加载器:各个项目的类加载器都是独立的,不会相互影响

System.out.println(System.getProperty("java.class.path"));
// C:\Users\Bug\eclipse-workspace3\bilibili\bin

类加载器的代理模式

交给其他加载器来加载指定的类。

双亲委托机制

父类加载器优先加载。如果父类不能加载,再让子类加载。
在这里插入图片描述

这种双亲委托机制可以很安全。

假设某人自己定义了一个java.lang.string,会一层一层向上传递,随后由最父类的核心包加载成功,而自己定义的永远不会被加载。

这样,核心类虽然能够自己定义,但是永远你无法使用到,保证了Java核心库的安全。

并不是所有的类加载器都采用了双亲委托机制。

Tomcat服务器也是用代理模式,所不同的是它子类优先。首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。因为可能对于服务器来说,在某些情况下,双亲委托机制不够灵活,并不能适应我们的需求。

本来想测试一下自定义java.lang.String的后果,然而,直接报错了,无法测试。
在这里插入图片描述


自定义类加载器(文件、网络、加密)

实现一个自定义类加载器的流程:

首先检查请求的类型是否已经被

(1)准备工作

C:\picture路径下创建一个Hello.java文件
在这里插入图片描述
Hello.java

package cn.hanquan.myloader;// 包名可以任意
public class Hello{
	public static void main(String[] args){
		System.out.println("Hello,World~~");
	}
}

编译运行一下
在这里插入图片描述
运行之后的效果:

C:\picture\cn\hanquan\myloader路径下,产生了Hello.class

在这里插入图片描述(2)编写自定义类加载器

  • 目录结构
    在这里插入图片描述
  • FileSystemClassLoader.java
package cn.hanquan.testloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/*
 * 自定义文件系统类加载器: 双亲委派式
 * 实现:自定义一个目录,传递进来,里面的class文件可以自动加载
 */
public class FileSystemClassLoader extends ClassLoader {

	private String rootDir;

	public FileSystemClassLoader(String rootDir) {
		this.rootDir = rootDir;
	}

	@Override
	protected Class<?> findClass(String className) throws ClassNotFoundException {
		Class<?> c = findLoadedClass(className);// 看这个类是否已经被加载过
		if (c != null) {// 已经加载过
			return c;
		} else {// 让父类AppClassLoader去加载
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(className);// 委派给父类加载
			} catch (ClassNotFoundException e) {
				// System.out.println("父类无法加载你的class,抛出ClassNotFoundException,已捕获,继续运行");
			}
			if (c != null) {// 父类成功加载
				System.out.println("父类成功加载");
				return c;
			} else {// 读取文件 转化成字节数组
				byte[] classData = getClassData(className);
				if (classData == null) {
					throw new ClassNotFoundException();// 手动抛出一个未加载到异常
				} else {
					c = defineClass(className, classData, 0, classData.length);
					return c;
				}
			}
		}
	}

	/*
	 * 传入cn.hanquan.myloader.Hello 返回字节数组c:/picture/cn/hanquan/myloader/Hello.class
	 */
	private byte[] getClassData(String className) {
		String path = rootDir + "/" + className.replace('.', '/') + ".class";
		// 可以使用CommonsIO将流中的数据转换为字节数组
		InputStream is = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			is = new FileInputStream(path);
			byte[] buffer = new byte[1024];
			int temp = 0;
			while ((temp = is.read(buffer)) != -1) {// 从输入流中读取
				baos.write(buffer, 0, temp);// 放进字节数组
			}
			return baos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {// 关闭输入流 字节数组流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (baos != null) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

使用同样的原理,把rootDir换成url,可以写一个网络的类加载器,将getClassData中的path改一下,然后输入流改一下即可。此处不再举例说明。

测试一下自定义的类加载器

  • Demo1.java
package cn.hanquan.testloader;
/*
 * 测试自定义的FileSystemClassLoader
 */
public class Demo1 {
	public static void main(String[] args) throws ClassNotFoundException {
		FileSystemClassLoader loader = new FileSystemClassLoader("c:/picture");// 加载器
		Class<?> c1 = loader.loadClass("cn.hanquan.myloader.Hello");// 加载这个类
		Class<?> c2 = loader.loadClass("cn.hanquan.myloader.Hello");// 加载这个类
		System.out.println("c1 的Class是  " + c1);
		System.out.println("c2 的Class是  " + c2);
		System.out.println("c1 的hashCode是  " + c1.hashCode());
		System.out.println("c2 的hashCode是  " + c2.hashCode());
		/*
		 * c1 c2是同一个加载器 同一个类 所以是同一个对象
		 * 
		 * 被同一个类加载器加载,并且是同一个类,JVM认作是同一个类
		 *  同一个类被不同不容的加载器加载,JVM会认为是不相同的类
		 */
		FileSystemClassLoader loader3 = new FileSystemClassLoader("c:/picture");// 换一个加载器
		Class<?> c3 = loader3.loadClass("cn.hanquan.myloader.Hello");// 加载这个类
		System.out.println("c3 的hashCode是  " + c3.hashCode());
	}
}

更多尝试:查看其他默认类加载器

  • Demo2.java
package cn.hanquan.testloader;

/*
 * 测试自定义的FileSystemClassLoader
 */
public class Demo2 {
	public static void main(String[] args) throws ClassNotFoundException {
		FileSystemClassLoader loader = new FileSystemClassLoader("c:/picture");// 加载器
		FileSystemClassLoader loader3 = new FileSystemClassLoader("c:/picture");// 换一个加载器

		Class<?> c1 = loader.loadClass("cn.hanquan.myloader.Hello");// 加载这个类
		Class<?> c2 = loader.loadClass("cn.hanquan.myloader.Hello");
		Class<?> c3 = loader3.loadClass("cn.hanquan.myloader.Hello");
		Class<?> c4 = loader.loadClass("java.lang.String");
		Class<?> c5 = loader.loadClass("cn.hanquan.testloader.Demo1");

		System.out.println("c1 的Class是  " + c1);
		System.out.println("c1 的hashCode是  " + c1.hashCode());
		System.out.println("c2 的Class是  " + c2);
		System.out.println("c2 的hashCode是  " + c2.hashCode());
		System.out.println("c3 的hashCode是  " + c3.hashCode());//同一个类被不同的加载器加载,JVM认为是不相同的类
		System.out.println("c3 的ClassLoader是  " + c3.getClassLoader());// 自定义的类加载器
		System.out.println("c4 的ClassLoader是  " + c4.getClassLoader());// 引导类加载器
		System.out.println("c5 的ClassLoader是  " + c5.getClassLoader());// 系统默认的类加载器
	}
}

(3)运行结果

Demo1.java的输出

c1 的Class是  class cn.hanquan.myloader.Hello
c2 的Class是  class cn.hanquan.myloader.Hello
c1 的hashCode是  1288141870
c2 的hashCode是  1288141870
c3 的hashCode是  1908153060

Demo2.java的输出

c1 的Class是  class cn.hanquan.myloader.Hello
c1 的hashCode是  966808741
c2 的Class是  class cn.hanquan.myloader.Hello
c2 的hashCode是  966808741
c3 的hashCode是  1908153060
c3 的ClassLoader是  cn.hanquan.testloader.FileSystemClassLoader@77556fd
c4 的ClassLoader是  null
c5 的ClassLoader是  jdk.internal.loader.ClassLoaders$AppClassLoader@28c97a5

对class进行加密操作

本示例使用逐位取反的方式进行加密。简单的取反操作示例如下:

	public static void main(String[] args) {
		int a = 3;
		System.out.println(Integer.toBinaryString(a));// 11
		System.out.println(Integer.toBinaryString(a ^ 0xff));// 11111100
	}

了解了逐位取反的方式之后,下面,开始我们的加密与解密~

(1)写一个加密工具

  • EncodeUtil.java

注意:加密不要改变class的文件名!class文件名称要保持与类名一致!

否则:在解密时,会抛出异常NoClassDefFoundError:wrong name

package cn.hanquan.testloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class EncodeUtil {

	public static void main(String[] args) {
		encode(new File("c:/picture/cn/hanquan/myloader/Hello.class"),
				new File("c:/picture/temp/cn/hanquan/myloader/Hello.class"));
		// class文件名称要保持与类名一致,否则NoClassDefFoundError:wrong name
		// 为了保持包名结构,我在文件夹中手动创建了一个空的C:\picture\temp\cn\hanquan\myloader
		// 要是不打这个package,应该就没有这么复杂了
	}

	public static void encode(File src, File dest) {
		FileInputStream fis = null;
		FileOutputStream fos = null;

		try {
			fis = new FileInputStream(src);
			fos = new FileOutputStream(dest);

			int temp = -1;
			while ((temp = fis.read()) != -1) {// 读取一个字节
				fos.write(temp ^ 0xff);// 取反输出
			}

		} catch (IOException e) {

		} finally {// 关闭流
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println("Finish Encoding!");
	}
}

运行之后,可以看到,C:\picture\temp\cn\hanquan\myloader路径下生成了Hello.class文件。
在这里插入图片描述
(2)尝试加载加密过的class文件

  • Demo3.java
package cn.hanquan.testloader;

/*
 * 测试简单的加密解密
 */
public class Demo3 {
	public static void main(String[] args) throws ClassNotFoundException {
		FileSystemClassLoader loader = new FileSystemClassLoader("c:/picture");// 加载器
		
		Class<?> c1 = loader.loadClass("cn.hanquan.myloader.Hello");// 加载未加密的类
		System.out.println("c1 的Class是  " + c1);
		
		Class<?> c2 = loader.loadClass("cn.hanquan.myloader.Hello_Encode");// 加载已加密的类
		System.out.println("c2 的Class是  " + c1);
	}
}

运行结果

显然,被加密的class文件是加载不到的。

抛出异常:是不兼容的格式。

因此,下一步,我们需要定义一个解密工具DecodeUtil.java
在这里插入图片描述(3)自定义解密工具类

  • DecodeClassLoader.java

DecodeClassLoaderFileSystemClassLoader.java稍微修改而来,不要忘了extends ClassLoader

package cn.hanquan.testloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DecodeClassLoader extends ClassLoader {
	
	private String rootDir;

	public DecodeClassLoader(String rootDir) {
		this.rootDir = rootDir;
	}
	
	@Override
	protected Class<?> findClass(String className) throws ClassNotFoundException {
		Class<?> c = findLoadedClass(className);// 看这个类是否已经被加载过
		if (c != null) {// 已经加载过
			return c;
		} else {// 让父类AppClassLoader去加载
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(className);// 委派给父类加载
			} catch (ClassNotFoundException e) {
				// System.out.println("父类无法加载你的class,抛出ClassNotFoundException,已捕获,继续运行");
			}
			if (c != null) {// 父类成功加载
				System.out.println("父类成功加载");
				return c;
			} else {// 读取文件 转化成字节数组
				byte[] classData = getClassData(className);
				if (classData == null) {
					throw new ClassNotFoundException();// 手动抛出一个未加载到异常
				} else {
					c = defineClass(className, classData, 0, classData.length);
					return c;
				}
			}
		}
	}

	public byte[] getClassData(String className) {
		String path = rootDir + "/" + className.replace('.', '/') + ".class";
		// 将流中的数据转换为字节数组
		InputStream is = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			is = new FileInputStream(path);
			byte[] buffer = new byte[1024];
			int temp = -1;
			while ((temp = is.read()) != -1) {// 读取一个字节
				baos.write(temp ^ 0xff);// 取反解密输出
			}
			return baos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (baos != null) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

(4)使用自定义的解密类加载器,加载经过加密的类

使用新定义的解密加载器,加载加密后的类,Demo如下:

  • Demo3.java
package cn.hanquan.testloader;

/*
 * 测试简单的加密解密
 */
public class Demo3 {
	public static void main(String[] args) throws ClassNotFoundException {
		
		FileSystemClassLoader loader = new FileSystemClassLoader("c:/picture");// 加载器
		Class<?> c1 = loader.loadClass("cn.hanquan.myloader.Hello");// 加载未加密的类
		System.out.println("c1 的Class是  " + c1);
		
		DecodeClassLoader deLoader = new DecodeClassLoader("c:/picture/temp");// 解密加载器
		Class<?> c3 = deLoader.loadClass("cn.hanquan.myloader.Hello");// 加载已加密的类
		System.out.println("c3 的Class是  " + c3);
	}
}

成功解密。运行结果:

c1 的Class是  class cn.hanquan.myloader.Hello
c3 的Class是  class cn.hanquan.myloader.Hello

线程上下文类加载器

在这里插入图片描述

package com.bjsxt.test;
/**
 * 线程上下文类加载器的测试
 * @author 尚学堂高淇 www.sxt.cn
 *
 */
public class Demo05 {
	public static void main(String[] args) throws Exception {
		ClassLoader loader = Demo05.class.getClassLoader();
		System.out.println(loader);
		
		
		ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
		System.out.println(loader2);
		
		Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("d:/myjava/"));
		System.out.println(Thread.currentThread().getContextClassLoader());
		
		Class<Demo01> c = (Class<Demo01>) Thread.currentThread().getContextClassLoader().loadClass("com.bjsxt.test.Demo01");
		System.out.println(c);
		System.out.println(c.getClassLoader());
	}
}

服务器类加载器原理和OSGI介绍

在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值