美文网首页
源码讲解双亲委托如何被破坏

源码讲解双亲委托如何被破坏

作者: 土豆肉丝盖浇饭 | 来源:发表于2019-11-02 16:25 被阅读0次

    什么是双亲委托

    双亲委托是类加载器的一个特性,当加载一个类的时候,首先会委托父加载器去加载,如果父加载器加载不到,才让子加载器尝试加载。

    这样做的好处。
    第一,保证了类的唯一性,不会被重复加载。
    其次是安全性,你无法通过实现同包同名类来篡改jvm的内部类的行为。(想象一下你引入的jar包存在这种类,如果没有这种安全性做保证,会发生啥)

    关于类加载器,我还想提两点

    1. 如果你使用A加载器加载了A类,那么A类中引用的B类,也会通过A加载器加载。不过A加载器加载B类的时候,还是会走双亲委托。
    2. 对于每个类加载器,他都会对应一些资源文件,这也确定哪些类会唯一被那个加载器加载。比如BootstarpClassLoader对应加载/jre/lib下的jar包。

    破坏双亲委托案例

    ServiceLoader

    ServiceLoader是jvm内部类,它被BootstrapClassLoader所加载。但是ServiceLoader所加载的扩展点类,不能被BootstrapClassLoader加载。所以在ServiceLoader会通过它内部的持有
    的loader类加载器加载这些扩展点类。


    loader会在ServiceLoader构造的时候传入。


    可以看到有两种情况,第一种使用我们传入的classloader,第二种使用线程上下文中的contextClassLoader。

    对于第一种情况,如果传入的classloader为null,也可以理解为你想使用BootStrapClassLoader去加载扩展类,会强制替换为AppClassLoader


    对于第二种情况,我们讲解下contextClassLoader绑定的classloader来自于哪里。

    默认情况下,子线程的contextClassLoader会继承父线程的contextClassLoader,而最顶层的父线程也就是main线程,它的contextClassLoader为AppClassLoader。

    当然你可以在使用ServiceLoader前,通过Thread.currentThread().setContextClassLoader(xxx);改变线程中的contextClassLoader。

    举一个需要改变的例子。

    比如的你的扩展点实现类存储在数据库中,这样就肯定用不了AppClassLoader了。

    Thread.currentThread().setContextClassLoader(customeClassLoader);
    ServiceLoader.load(xxxInterface.class)
    

    哇塞,这好像是一种远程spi的解决方案。

    个人感觉ServiceLoader这种处理方式,没有破坏双亲委托,在类加载器内部还是符合双亲委托的,只算是破坏了常规的类加载流程,这种取巧方式只是为了解决这种特定的问题。而tomcat的WebAppClassLoader那是真的破坏了双亲委托。

    Tomcat WebAppClassLoader

    在我们实现自定义ClassLoader的时候,推荐我们只实现 findClass方法,而不建议我们重载loadClass方法。

    为啥?因为双亲委托就封装在这个方法中,如果我们破坏,可能会带来不可预知的风险。比如一个类加载了两份。

    但是Tomcat真正破坏了双亲委托,显然是修改了这个方法。

    看下WebappClassLoade的loadClass方法实现

        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
            synchronized (getClassLoadingLock(name)) {
                if (log.isDebugEnabled())
                    log.debug("loadClass(" + name + ", " + resolve + ")");
                Class<?> clazz = null;
    
                // Log access to stopped class loader
                checkStateForClassLoading(name);
                //缓存
                clazz = findLoadedClass0(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Returning class from cache");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
                //缓存
                clazz = findLoadedClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Returning class from cache");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
    
                String resourceName = binaryNameToPath(name, false);
    
                ClassLoader javaseLoader = getJavaseClassLoader();
                boolean tryLoadingFromJavaseLoader;
                try {
                    URL url;
                    if (securityManager != null) {
                        PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                        url = AccessController.doPrivileged(dp);
                    } else {
                        url = javaseLoader.getResource(resourceName);
                    }
                    tryLoadingFromJavaseLoader = (url != null);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    tryLoadingFromJavaseLoader = true;
                }
    
    
                //如果是jvm内部类 走javaseLoader=extclassloader
                if (tryLoadingFromJavaseLoader) {
                    try {
                        clazz = javaseLoader.loadClass(name);
                        if (clazz != null) {
                            if (resolve)
                                resolveClass(clazz);
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
    
                if (securityManager != null) {
                    int i = name.lastIndexOf('.');
                    if (i >= 0) {
                        try {
                            securityManager.checkPackageAccess(name.substring(0,i));
                        } catch (SecurityException se) {
                            String error = "Security Violation, attempt to use " +
                                "Restricted Class: " + name;
                            log.info(error, se);
                            throw new ClassNotFoundException(error, se);
                        }
                    }
                }
    
                //delegateLoad=true 表示先走commonclassloader
                //filter方法用于判断当前加载类是否为tomcat本身需要加载的类
                //delegate默认为false
                boolean delegateLoad = delegate || filter(name, true);
    
                //delegateLoad=true的情况下,交由parent加载
                if (delegateLoad) {
                    if (log.isDebugEnabled())
                        log.debug("  Delegating to parent classloader1 " + parent);
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled())
                                log.debug("  Loading class from parent");
                            if (resolve)
                                resolveClass(clazz);
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
    
                //从我们的war包路径下搜索类
                if (log.isDebugEnabled())
                    log.debug("  Searching local repositories");
                try {
                    clazz = findClass(name);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from local repository");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
    
                //如果delegateLoad为false,交由parent加载
                if (!delegateLoad) {
                    if (log.isDebugEnabled())
                        log.debug("  Delegating to parent classloader at end: " + parent);
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled())
                                log.debug("  Loading class from parent");
                            if (resolve)
                                resolveClass(clazz);
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
            }
    
            throw new ClassNotFoundException(name);
        }
    

    tomcat8在默认配置下,类加载器继承结构为

         Bootstrap
              |
             EXT
              |
             APP
              |
           Common
           /     \
      Webapp1   Webapp2 ...
    

    如果在 conf/catalina.properties对server.loader 或shared.loader 做了配置,类加载器继承结构如下

      Bootstrap
          |
         EXT
          |
         APP
          |
        Common
         /  \
    Server  Shared
             /  \
       Webapp1  Webapp2 ...
    

    我们默认使用第一种方式讲解,也是是上面源码中的parent为CommonClassLoader

    CommonClassLoader默认加载以下路径的资源

    common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
    

    修改后的loadClass方法大致逻辑如下(delegate默认为false)

    1. 先走class加载缓存
    2. 如果class为jvm内置类,走extClassLoader
    3. 如果filter方法返回true,走commonClassLoader
    4. 通过WebAppClassLoader加载/WEB-INF/classes和/WEB-INF/lib/*.jar中的类
    5. 如果filter方法返回false,走commonClassLoader

    filter方法内的大致逻辑是,判断该class是否是tomcat自己的类。



    也就是是否是tomcat源码中这些类。

    从loadClass逻辑中,对于webapp中的类,直接通过WebAppClassLoader去加载,不走双亲委托机制。

    这种做法能保证tomcat中部署的2个应用的隔离。如果没有这个改造,那么A应用加载版本1的类后,B应用也被迫使用这个版本,如果B依赖的版本和A不同,那么B应用可能会发生错误。

    相关文章

      网友评论

          本文标题:源码讲解双亲委托如何被破坏

          本文链接:https://www.haomeiwen.com/subject/xukqbctx.html