类加载器原理

将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;}@Overrideprotected 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));// 11System.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;}@Overrideprotected 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介绍

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

  1. Java类加载器(二)——自定义类加载器

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  2. 基于Java的(SM2_SM3_SM4)国密算法, 加密解密工具类及测试demo

    编写本文的同时借鉴了多位同学写的demo, 最后发现这位同学写的最详细, 放上该作者的链接, 原版链接: https://download.csdn.net/download/ererfei/9474 ...

  3. 使用mybatis拦截器实现字段加密解密

    前言 .项目中我们存储一些用户信息的使用后根据规定,不可以存储明文,尤其是密码,实现的办法有好多种,今天承接上一篇文章mybatis拦截器,利用拦截器实现使用注解的方式在数据插入前进行加密,查询是自动 ...

  4. java nodejs aes_Java与Node.js利用AES加密解密出相同结果的方法示例

    前言 工作中遇到nodejs端通过aes加密,安卓客户端Java解密,同样nodejs也需要解密安卓客户端加密过来的内容,发现两个加密结果不一样,查询资料发现java端需要对密钥再MD5加密一遍,以下 ...

  5. java rsa字符串_使用RSA对字符串加密解密

    本文介绍,使用命令行和编程语言(nodejs和java),将字符串用RSA加密和解密. 命令行:openssl 使用Mac,openssl自带了,以下使用步骤在Mac OSX 10.9.2下测试通过. ...

  6. Java从接触到放弃(二十四)---类加载

    Day Twenty-Four 类加载 类的加载过程 当一个程序主动使用某一个类的时候,如果说这个类还没有被加载到内存中,则系统会通过"类的加载","类的链接" ...

  7. Spring Cloud Config服务端配置细节(二)之加密解密

    在微服务架构中,由于独立的服务个数众多,加上前期测试工作量大,一些原本由运维人员维护的敏感信息会被我们直接写在微服务中,以提高开发效率,但是这种明文存储方式显然是非常危险的,所以我们要对这些信息进行加 ...

  8. 如何计算维吉尼亚密码?Java实现维吉尼亚密码的加密解密算法

    文章目录 如何计算维吉尼亚密码? Java实现加密算法 Java实现解密算法 参考博客 如何计算维吉尼亚密码? 计算维吉尼亚密码有2种方式,一种是根据密码表查找,另一种是手动计算方法. 1.密码表查找 ...

  9. ccs加载out文件_类加载流程、类加载机制及自定义类加载器详解

    原文:juejin.im/post/5cffa528e51d4556da53d091 一.引言 当程序使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.链接.初始化三个步骤对该类进行类加载 ...

最新文章

  1. CG游戏道具全流程制作视频教程 Artstation – Stylized Game Asset
  2. django rest-framework 1.序列化 一
  3. 推荐一个yaml文件转json文件的在线工具
  4. STM32F1笔记(一)GPIO输出
  5. mysql8安装步骤及排坑
  6. 用java实现串匹配问题_java实现字符串匹配问题之求最大公共子串
  7. 脑波控制机械手,双手打字也无法自证清白| Science Robotics
  8. 2018年IEEE Fellow名单:32位中国学者入选,清华成最大赢家
  9. opengl入门6。1
  10. C#调用exe工作目录
  11. 【大数据部落】银行信用数据SOM神经网络聚类实现
  12. Zim学习笔记 (Fedora)
  13. 最好用的mac屏幕分辨率修改器:SwitchResX for Mac
  14. javascript监听页面刷新事件
  15. CHD 常用web端口
  16. 斐波那契数列(入门c语言)
  17. 时尚手表品牌PaulHewitt,手表手链超时尚品牌礼物
  18. 数据库管理工具heidiSQL的基本使用
  19. 用css动态实现圆环百分比分配——初探css3动画
  20. 汪汪汪WDG--看门狗的作用

热门文章

  1. oracle输出异常,表导出出现异常,无法继续。
  2. matlab扩充内存,matlab扩大内存的方法
  3. Linux压缩那些事
  4. 3.IDA-数据显示窗口(导出窗口、导入窗口、String窗口、...窗口)
  5. 逆向工程核心原理学习笔记(四):检索API方法2-设置断点
  6. UDP用打洞技术穿透NAT的原理与实现
  7. 抓取dump的头文件
  8. 一个内核网络漏洞详解|容器逃逸
  9. 三流面试聊JDK,二流面试聊JVM,一流面试……
  10. 眺望全真互联时代!TVP音视频技术闭门会闪耀上海