浅谈Java里的啃老族-双亲委派

这里大家好, 我是老K.今天详细双亲委派.


前几天写过一次了.

对了,最近有朋友问,SPI和双亲委派打破相关的事.  其实这个很简单, 就记得一点就行. 类的加载器是一个树状结构, 不同加载器加载上来的同一个类是不相同的,equals=false.  当你需要不同classLoader加载出来的类equals=true,那就用双亲委派,往上冒泡,交给自己的父节点来处理. 需要他们不相等, 就打破他.

J K L,公众号:K字的研究如何像使用Lombok一样来优雅生成垃圾代码?

不过这个不够长, 我们扩展一下.详细写一下.

必正乎名也

parent delegation model 一般被翻译为 双亲委派模型. 但是这个名词的翻译, 其实欠妥.

  1. 首先这里面没有, parent是单数的.
  2. 其次, delegation, 更多时候被翻译为委托, 是的,就是托儿所那个.

这玩意儿要是按照俗文化来翻译, 其实就叫:找你爸爸模型. 我这句也够邪的,还好意思正名.

浅谈Java里的啃老族-双亲委派

找你爸爸模型

这个模型的原则其实很简单.

比如, 类似于这个图.

浅谈Java里的啃老族-双亲委派

浅谈Java里的啃老族-双亲委派

这其实是一个请求往上冒泡的过程.

  1. 你是一个 ClassLoader
  2. 有人找你要 Class
  3. 如果你有爸爸,找你爸爸要
  4. 如果没爸爸或者爸爸也没有,自己处理

浅谈Java里的啃老族-双亲委派


实现在代码里也很简单, 把上面的原则翻译成代码就行了. 比如我们可以自己实现一个ClassLoader从 Base64 编码的 String 里读出一个类.:

package sh.now.afk.classloader;

import java.io.IOException;
import java.util.Base64;
import java.util.Map;

public class KlassLoader extends ClassLoader {
    Map<String, String> map = Map.of("sh.now.afk.classloader.K""yv66vgAAADcAPgoACQAWCQAIABcJABgAGQoAGgAbCgAaABwSAAAAIAoAIQAiBwAjBwAkAQAEbG9hZAEAAVoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAGkxzaC9ub3cvYWZrL2NsYXNzbG9hZGVyL0s7AQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQAGSy5qYXZhDAAMAA0MAAoACwcAJQwAJgAnBwAoDAApACoMACsALAEAEEJvb3RzdHJhcE1ldGhvZHMPBgAtCAAuDAAvADAHADEMADIAMwEAGHNoL25vdy9hZmsvY2xhc3Nsb2FkZXIvSwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEAFWdldENvbnRleHRDbGFzc0xvYWRlcgEAGSgpTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsKADQANQEAB2xvYWQgSwEBABdtYWtlQ29uY2F0V2l0aENvbnN0YW50cwEAKyhMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylMamF2YS9sYW5nL1N0cmluZzsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgcANgwALwA6AQAkamF2YS9sYW5nL2ludm9rZS9TdHJpbmdDb25jYXRGYWN0b3J5BwA8AQAGTG9va3VwAQAMSW5uZXJDbGFzc2VzAQCYKExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL2ludm9rZS9DYWxsU2l0ZTsHAD0BACVqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwAQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzACEACAAJAAAAAQAIAAoACwAAAAIAAQAMAA0AAQAOAAAALwABAAEAAAAFKrcAAbEAAAACAA8AAAAGAAEAAAADABAAAAAMAAEAAAAFABEAEgAAAAgAEwANAAEADgAAAD4AAgAAAAAAGgOzAAKyAAO4AAS2AAW6AAYAALYABwSzAAKxAAAAAQAPAAAAEgAEAAAABAAEAAcAFQAIABkACQADABQAAAACABUAOQAAAAoAAQA3ADsAOAAZAB0AAAAIAAEAHgABAB8=");

    public static void main(String[] args) throws ClassNotFoundException, IOException {
        Class<?> Klass = Class.forName("sh.now.afk.classloader.K",
                true,
                new KlassLoader());
        System.out.println(Klass);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (this.getParent() != null) {
            try {
                clazz = this.getParent().loadClass(name);
                return clazz;
            } catch (ClassNotFoundException e) {}
        }
        if (clazz != null) {
            return clazz;
        }
        return loadClassFromBase64String(name);
    }

    private Class<?> loadClassFromBase64String(String name) throws ClassNotFoundException {
        System.out.println("base64 class load");
        String source = map.get(name);
        byte[] decode = Base64.getDecoder().decode(source);
        return defineClass(name, decode, 0, decode.length);
    }
}

很容易, 是不是?我们已经可以从 Base64 字符串里加载 class 了.

5W1H

用经典的 5W1H 来学习 classLoader.

浅谈Java里的啃老族-双亲委派

class 加载的核心代码, 其实就是:

defineClass(name, decode, 0, decode.length);
  1. 读取 class 对应的byte[]
  2. 然后调用defineClass.

HOW

这个defineClass是怎么加载How的问题.

WHAT

这个byte[]对应是加载什么What的问题.

WHERE

各种各样的 classLoader, 就是花式解决, 从什么地方来获取这个byte[].是一个Where的问题.

WHO

parent delegation 模型, 是解决由谁来获取这个byte[].是一个Who的问题.

WHEN

父 loader 找不到, 自己找, 其实就是一个什么时候WHEN的问题.

WHY

WHY 的问题. 为什么要用委托, 相信开头第一段已经说的很明白了.

Java 里不使用委托模型的地方

有些地方, 会提到 Java 里有几处不使用委托模型的地方. 如果知道 WHY 使用的话, 相信已经有答案了.

这里我故意用了不使用模型,而不是打破原则这样的字眼.

因为,在Java 官方 The Java Class Loading Mechanism里用的措辞, 

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.

The Java Class Loading Mechanism https://docs.oracle.com/javase/tutorial/ext/basics/load.html

也是Mechanism,Model,Idea. 委托本来就不是一个原则,无所谓打破不打破.

我们重新总结下这几处不使用委托的地方.

1. 代码写的早于模型出现

Java 最强的就是兼容性,早于模型出现,没有使用,十分合理

2. SPI 扩展

这部分其实也不局限于 SPI. 

所有你明知道父 loader 绝不可能支持的加载形式, 都可以考虑不去问 loader 了.比如我们写的这个从 base64 字符串里加载的.如果我再写个从数据库,配置文件,等等地方加载类的,一样可以不使用委托模型.

毕竟,很多尝试用父 loader 来加载的方法,用的是 try catch. 异常毕竟是会拖慢进程的. 明知的情况下,可以考虑不用委托.

3. 类型卸载

刚刚的 loader 我们留了一个 bug,没有对类进行缓存.按照要求, 被加载过的类要直接返回,加载上来的类是要缓存的.

The loadClass method in ClassLoader performs these tasks, in order, when called to load a class:

  1. If a class has already been loaded, it returns it.
  2. Otherwise, it delegates the search for the new class to the parent class loader.
  3. If the parent class loader does not find the class, loadClass calls the method findClass to find and load the class.

有加载就有卸载, 如果交给父 loader 去加载, 他缓存了, 也并不一定会提供良好的卸载方法.

比如,有一个场景. 我们在一个 Tomcat 这种容器里运行了 2 个不同的warA 和 B. 整个 Tomcat 其实只有一个进程. classLoader树 🌲 的最顶层肯定是个单根. 这两个war都使用了同一个class,比如 Spring 啥的类.

假如在这里用委托模型, 那么这两个war需要的类,都缓存在高层祖先那里.当我们需要卸载 B 相关内容, 把 B 对应的类从缓存里删了….A WAR 也会被影响到. 只能大叫一句, FML.

结语

好了, 相信大家已经很熟练的知道什么是找爸爸模型,为什么有,什么时候可以用,什么时候可以不用了. 祝大家在被面试官问到”Java 为什么要 3 次打破双亲委派”的时候, 可以给面试官科普下.(!!!千万不要!!!这是玩笑)

延伸

Parent Delegate了, 有什么Son Delegate吗?

其实吧, 这个很常见.委托,就是日常所说的那个委托, 把事情交给别人办,就是委托. Java 里其实到处都是这种甩锅现场.

  1. 接口
  2. 抽象类
  3. 抽象方法 这不都是大型甩锅现场吗?

在 C#的语言里, 甚至有一种语法,就叫Delegate不过说实话, 我其实挺羡慕这个语法结构的. 他的特点是:

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用. 在方法重载的上下文中,方法的签名不包括返回值。 但在委托的上下文中,签名包括返回值。 换句话说,方法和委托必须具有相同的返回类型。

C# https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/​


他的用入参+返回值确定一个签名. 是不是感觉有哪里不对, 好像很熟悉?

是的,确实熟悉,因为前面几天说到的, java.util.function下面的,像LongToIntFunction这种,表示入参是long,返回是int.这特么不就是委托吗??

后话

这篇主要是另一篇太短了,写出来凑个数.太长也不合适.classLoader 的树状结构是怎么回事,bootStrapLoader 啥的我也没说. 还是不提了吧. 写得好的有很多, 我也懒得写.可以去看这里

不过倒是可以分享下, 一个排版工具,jgrapht.对于数组链表之类,图怎么设计都是很容易的.对于图论相关内容非常复杂.有长篇累牍的东西,来处理怎么排版,怎么让元素不互相影响,等等等等. 这个包很不错,玩儿图记得+1.


原文始发于微信公众号(K字的研究):浅谈Java里的啃老族-双亲委派

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24871.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!