Java自定义类加载器全解

1、为什么要自定义类加载器呢?有什么好处①、隔离加载类
在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境 。比如:阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包 。再比如:Tomcat这类Web应用服务器,内部自定义了好几种类加载器,用于隔离同一个Web应用服务器上的不同应用程序 。
两个jar包内都存在相同类名且包名相同,如果没有隔离加载类,则会报错,如:两个版本的jar
②、修改类加载方式
类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载
③、扩展加载源
比如从数据库、网络、甚至是电视机机顶盒进行加载
④、防止源码泄露
JAVA代码容易被编译和篡改,可以进行编译加密 。那么类加载也需要自定义,还原加密的字节码 。
通常Java系统想增加License(授权),就可以通过自定义类加载器实现 。
具体实现我们在最后面测试 。
2、自定义类加载器的使用场景①、实现类似进程内隔离,类加载器实际上用作不同的命名空间,以提供类似容器、模块化的效果 。例如,两个模块依赖于某个类库的不同版本,如果分别被不同的容器加载,就可以互不干扰 。这个方面的集大成者是JavaEE和OSGI、JPMS等框架 。
②、应用需要从不同的数据源获取类定义信息,例如网络数据源,而不是本地文件系统 。或者是需要自己操纵字节码,动态修改或者生成类型 。
3、类加载器注意点在一般情况下,使用不同的类加载器去加载不同的功能模块,会提高应用程序的安全性 。但是,如果涉及Java类型转换,则加载器反而容易产生不美好的事情 。在做Java类型转换时,只有两个类型都是由同一个加载器所加载,才能进行类型转换,否则转换时会发生异常 。
4、自定义类加载器实现方式Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类 。
在自定义ClassLoader的子类时候,我们常见的会有两种做法:

  • 方式一:重写loadClass()方法
  • 方式二:重写findClass()方法
对比
这两种方法本质上差不多,毕竟loadClass()也会调用findClass(),但是从逻辑上讲我们最好不要直接修改loadClass()的内部逻辑 。建议的做法是只在findClass()里重写自定义类的加载方法,根据参数指定类的名字,返回对应的Class对象的引用 。
loadClass()这个方法是实现双亲委派模型逻辑的地方,擅自修改这个方法会导致模型被破坏,容易造成问题 。同时,也避免了自己重写loadClass()方法的过程中必须写双亲委托的重复代码,从代码的复用性来看,不直接修改这个方法始终是比较好的选择 。
当编写好自定义类加载器后,便可以在程序中调用loadClass()方法来实现类加载操作 。
ClassLoader类中loadClass()方法源码,可以看出内部调用了findClass()方法
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 通过类的全限定名称(加包名)调用findClass方法查找类c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}说明
  • 自定义加载器的父类加载器是系统类加载器
  • JVM中的所有类加载都会使用java.lang.ClassLoader.loadClass(String)接口(自定义类加载器并重写java.lang.ClassLoader.loadClass(String)接口的除外),连JDK的核心类库也不能例外 。
5、通过重写findClass()方法实现自定义类加载器①、自定义类com.lc.Demo
package com.lc;/** * @author liuchao * @date 2023/3/25 */public class Demo {public void hello() {System.out.println("myClassLoader hello");}}通过javac Demo 将 类编译为Demo.class 文件放入/Users/liuchao/Desktop/文件夹下


推荐阅读