如何快速写一个违背双亲委托机制的classloader

简介: 很多情况下,不得以必须写个classloader来满足需求。例如你一个工程里你想用相同的数据库的多个版本,自己制定了一个jar包目录,没有classloader管理等等。

很多情况下,不得以必须写个classloader来满足需求。例如你一个工程里你想用相同的数据库的多个版本,自己制定了一个jar包目录,没有classloader管理等等。如果是一个遵循java已经规定好的机制的classloader(双亲委托以及加载依赖类的classloader继续加载剩下的类)。直接继承classloader就可以。(一般选择urlclassloder,他帮你实现了不少功能)。但是往往有需求需要加载两个相同的Jar包,例如web应用中的webappclassloader。不同的应用的相同war包都需要被同级的加载,优先级高于was的lib目录。

编写classloader

加入缓存

classloader的本质是加载字节码到内存,而且相同字节码不能被重复加载,所以我们需要加入缓存,简单选择一个hashmap就可以

Map<String,Class<?>> classMap =new HashMap<String,Class<?>>();

覆写关键方法

不论你是class.forname,还是loadclass方法的调用,最后都是要调用loadClass的,所以这里一定要重写这个方法,这里就要加入破坏双亲委托机制的逻辑。

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> classLoaded = classMap.get(name);
        if (classLoaded != null) {
            return classLoaded;
        }
        Class<?> findClass = null;
        try {
            findClass = findClass(name);
        } catch (Exception e) {
            //还可以从父类查找,这个异常吞掉,如果没有父类会抛出
        }
        if (findClass != null) {
            classMap.put(name, findClass);
            return findClass;
        }
        return super.loadClass(name);
    }

逻辑很简单,先从缓存找是否已经加载了class,对已经加载的就直接返回,防止重复加载。此处调用了findClass方法,findClass是可以覆写的,这里为了简洁的实现,就不再覆写了。
拿这样的classloader如果有重复的jar包可能是有问题的。问题就在他的findclass中。findClass找到资源后会通过defineClass(String name, Resource res)来加载类,这里最后会调用getPackage来获取包。下面是找的流程。

    protected Package getPackage(String name) {
        Package pkg;
        synchronized (packages) {
            pkg = packages.get(name);
        }
        if (pkg == null) {
            if (parent != null) {
                pkg = parent.getPackage(name);
            } else {
                pkg = Package.getSystemPackage(name);
            }
            if (pkg != null) {
                synchronized (packages) {
                    Package pkg2 = packages.get(name);
                    if (pkg2 == null) {
                        packages.put(name, pkg);
                    } else {
                        pkg = pkg2;
                    }
                }
            }
        }
        return pkg;
    }

这里很明显的,先从已经加载的包中查找,如果没有就先从父classloader找,最后又进行了双亲委托机制。所以这个地方也需要覆写。

    protected Package getPackage(String name) {
        return null;
    }

比较暴力直接返回空。在后面的判断中,如果此处返回null,后续就会重新新建一个对象,然后放入一个缓存结构,还是一个hashmap

    private final HashMap<String, Package> packages = new HashMap<>();

为空的结果只是更新一下缓存。这里没有对类加载产生问题。

最终结果

public class MyClassloader extends URLClassLoader {
    public MyClassloader(URL[] urls) {
        super(urls);
    }

    Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> classLoaded = classMap.get(name);
        if (classLoaded != null) {
            return classLoaded;
        }
        Class<?> findClass = null;
        try {
            findClass = findClass(name);
        } catch (Exception e) {
            //还可以从父类查找,这个异常吞掉,如果没有父类会抛出
        }
        if (findClass != null) {
            classMap.put(name, findClass);
            return findClass;
        }
        return super.loadClass(name);
    }

    @Override
    protected Package getPackage(String name) {
        return null;
    }
}

只要覆写两个方法就好。这样就能比较小的改造成一个破坏双亲委托机制的classloader。

目录
相关文章
|
4月前
|
人工智能 缓存 JavaScript
SpringBoot 公共字段自动填充的6种方法
本文深入探讨了在Java开发中如何高效维护公共字段的多种解决方案。首先分析了手动设置公共字段带来的代码重复、维护成本高和易遗漏等问题,接着介绍了使用MyBatis-Plus自动填充、AOP统一处理等基础与进阶方案,实现字段自动赋值。文章还涵盖了多数据源适配、分布式ID生成、空指针防护、字段覆盖问题解决、性能优化以及操作日志追踪等生产环境中的最佳实践与避坑指南。最终通过方案组合使用,显著提升了开发效率与系统稳定性,为构建高质量企业级应用提供了有力支撑。
253 0
|
搜索推荐 Java 开发者
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 问题处理
【5月更文挑战第14天】org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 问题处理
5388 1
|
Java 数据库连接 Spring
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could
这个错误通常出现在使用Spring Boot进行数据库连接时。错误信息表明Spring Boot未能配置一个DataSource,因为没有指定'url'属性,并且没有发现默认的数据库连接。
5942 0
|
存储 前端开发 Java
Java 中的 ArrayList 和 LinkedList
列表是一种数据结构,为了方便理解,我们可以认为它是动态的数组。众所周知,数组的大小在定义的时候就固定了,不可改变,那么它就无法存储超过它容量的数据,而列表的容量是无限大的,因为它可以随着其存储内容的大小进行动态的变化(包括容量扩增和缩小),这和 java.util.Vector 很像,但又不完全相同。Java 中对列表的实现有两种,ArrayList 和 LinkedList。
174 1
|
存储 缓存 Java
面试必杀技,讲一讲Spring中的循环依赖
纠正业界对循环依赖的几个错误认知,明确三级缓存的真正作用
29216 18
|
Java 关系型数据库 MySQL
基于Springboot的三方线上美食商城系统
基于Springboot的三方线上美食商城系统
364 0
基于Springboot的三方线上美食商城系统
|
缓存 NoSQL Redis
一步一步学习Redis——脚本功能的相关命令
一步一步学习Redis——脚本功能的相关命令
一步一步学习Redis——脚本功能的相关命令
|
存储 JSON 缓存
5. JsonFactory工厂而已,还蛮有料,这是我没想到的(中)
5. JsonFactory工厂而已,还蛮有料,这是我没想到的(中)
|
Java Windows
JVM06_方法区的概述、内部结构、演变、常量池、运行时常量池、垃圾回收(二)
③. 方法区的演进细节 ④. 设置方法区大小 ⑤. 常量池的理解
179 0
JVM06_方法区的概述、内部结构、演变、常量池、运行时常量池、垃圾回收(二)
|
JSON fastjson Java
Fastjson 使用
fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean序列化为JSON字符串,也可以从 JSON 字符串反序列化到 JavaBean。 功能完备: 支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。 下载 jar包 或者配置 maven 依赖:
3929 0