Java自定义ClassLoader实现插件类隔离加载 - 原理篇

书接上回

在 Java自定义ClassLoader实现插件类隔离加载文章中,我们通过

自定义ClassLoader + 插件独立打包引入的方式,实现了同依赖不同版本的隔离加载

这次咱们来分析下具体实现原理

 

打破双亲委派机制

首先,双亲委派机制不会自己去尝试加载类,而是把请求委托给父加载器去完成,依次向上

其次,思考一个问题

以前使用Tomcat部署项目时,webapp目录下可能部署多个项目的war包

这些war包中可能包含同一个类,类全限定名一样,但是实现不一样

那么Tomcat如何保证他们不会冲突呢?

Tomcat正是使用了打破双亲委派机制,给每一个应用创建独立的类加载器实例WebAppClassLoader,并重写loadClass核心方法,使得优先加载当前应用目录下的类

 

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded(首先,检查类是否已经加载)
Class<?> 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();
// 查找Class资源 c
= findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

 

先分析一波loadClass的源码

第一行用synchronized关键字做了对象锁处理,这里说点额外话题,为啥对象锁用的getClassLoadingLock(name),而不是直接用name作为对象锁,推荐一篇博文讲的非常详细:https://www.cnblogs.com/thisiswhy/p/15892044.html

进入锁以后,通过findLoadedClass方法,先找已经加载过的Class

已经加载过的Class中找不到的话,再通过parent.loadClass(name, false)委托父加载器加载

这段逻辑就是双亲委派的处理

 

类加载规则

如果一个类由类加载器A加载,那么这个类的依赖类也是由「相同的类加载器」加载。

 

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        // AccessController提供了一个默认的安全策略执行机制,它使用栈检查来决定潜在不安全的操作是否被允许
        // doPrivileged方法用来终端没有权限不被允许的操作
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    // 获取Class路径
                    String path = name.replace('.', '/').concat(".class");
                    // 加载Class资源
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 加载Class资源,定义Class类
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

 

总结

我们通过自定义ClassLoader,清空了父加载器,使得打破了双亲委派机制,不会通过缓存加载到AppClassLoader中已加载过的类

加载的Class类中,其它的依赖类,也是经过我们自定义的ClassLoader进行的加载

因此,我们加载不同的插件包时,可以实现类的隔离加载

posted @ 2022-02-15 15:46  codest  阅读(292)  评论(0编辑  收藏  举报