Tomcat如何打破双亲委派

简介

这里是tomcat源码阅读系列的第二篇文章,目标是读懂并介绍tomcat如何打破java默认的双亲委派类加载模型~

前情提要

关于tomcat源码的基本信息可以看我的上篇文章
这里实践验证一下tomcat打破双亲委派的必要性[1]

file

我们在同一个tomcat中运行两个命名完全相同的webapp,将他们的context分别设置为根和/2/

  • 如果采用传统双亲委派,这两个webapp中的HelloWorldServlet类不能同时存在
  • 预期结果是他们同时存在,访问两个contex可以定位到两个类
    实验结果:
    file

    验证成功,说明tomcat使用的确实不是传统的双亲委派

    断点调试

    web类装载(打破委派)

    如果要实现这个功能肯定是要自定义Classloader的,所以直接定位到org.apache.catalina.loader包,在WebappClassLoaderBasefindClass方法上打一个断点调试

    findclass 栈

    从方法栈中可以看出:

  1. StandarContextstartInternal方法中发射[2]了一个event,激活了ContextConfig中的监听逻辑;

    ContextConfig context组件的启动事件侦听器,配置该context的属性,以及关联的定义的servlet。

  2. 监听发现是一个Lifecycle.CONFIGURE_START_EVENT,会执行非常复杂的逻辑(几乎两百行代码的一个方法),和我们的研究目标有关的大概有两部分

    1. 从webxml中读取servlet并标记为context的child(还未加载)

      private void configureContext(WebXml webxml) {
      //省略大量无关代码…………
      for (ServletDef servlet : webxml.getServlets().values()) {
          Wrapper wrapper = context.createWrapper();
          //wrapper是servlet中函数的包装,将wrapper放入context的child数组
           context.addChild(wrapper);
      }
      //…………
    2. 调用loadApplicationAnnotations(Context context)加载context的child

      file

      从方法名看出,context的child就是javaWeb三大件。

  3. 在步骤2的loadApplicationAnnotations(Context context)方法中会使用所属context的ClassLoader.loadClass()加载servlet类;这里context的默认加载器是ParallelWebappClassLoader,且不同context的loader不同

    file

    其loadClass方法相较于传统有所不同,步骤如下:

    1. 加载过程会首先尝试使用缓存查看类是否被加载过,最上层缓存是WebappClassLoader中的一个简单的ConcurrentHashMap,key是class文件的路径;第二层缓存是jvm中ClassLoader中自带的[3]
    2. 确认未命中缓存后,尝试使用javase默认的classloader加载这个类,目的是防止打破双亲委派后出现webapp应用覆盖javase应用的风险

      这里使用了getResource方法来加载,因为他不会触发昂贵的ClassNotFoundException[4]

    3. 如果这个类是合法的就进入正式加载过程;首先判断是否需要走传统的委派模式[5]
      重头戏来了
      这里就是打破双亲委派的重点,也就是每个context自己直接加载这个Class而不是委派给双亲加载器[6],从而使得包名重复的类可以同时加载

      boolean delegateLoad = delegate || filter(name, true);
      //filter()会根据name对该类是否属于webapp进行判断
    4. 调用findClass()查找class文件[7]
    5. 调用Class.forName()正式加载;
  4. 至此,两个servlet就无冲突的加载成功了,web可以正常运行

总结

虽然文章看起来很长,实际上核心逻辑就是tomcat自定义了一个classLoader(WebappClassLoader),并重写了他的loadClass方法,在其中不再将servlet类进行委派而是直接加载;而不同的context持有不同的webClassLoader实例,所以即使不同context中有相同包名的类也不会相互影响。

Tips

  1. ^后面读源码时也要用到这个带冲突的环境,所以事先配好
  2. ^fire翻译成发射好怪啊,为什么要用fire呢
  3. ^本地方法,看不到实现;估计实现原理大差不差
  4. ^如果程序设计正常那么每个webapp中合法的类都会触发这个异常,java处理异常时会有许多中断,性能较差
  5. ^我们自定义的servlet是不需要委派的,但是tomcat提供的DefaultServlet与JspServlet会走委派;一个负责处理static资源一个负责解析jsp
  6. ^还记得么,在第3点中指出负责加载servlet类的是其所属的context的loader
  7. ^没错 直到现在才开始找class文件并为加载做准备,之前最多只是判断这个class是否存在,这里才开始要真正读取内容

评论

  1. 博主
    Windows Firefox
    2 年前
    2023-3-02 0:00:03

    陆续写了小一周,源码读的还不是很熟练~
    (当然也是事太多 总摸鱼,只能断断续续写)

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇