深入拆解Tomcat & Jetty
李号双
eBay技术主管
立即订阅
6067 人已学习
课程目录
已完结 44 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | Java程序员如何快速成长?
免费
模块一 必备基础 (4讲)
01 | Web容器学习路径
02 | HTTP协议必知必会
03 | 你应该知道的Servlet规范和Servlet容器
04 | 实战:纯手工打造和运行一个Servlet
模块二 整体架构 (9讲)
05 | Tomcat系统架构(上): 连接器是如何设计的?
06 | Tomcat系统架构(下):聊聊多层容器的设计
07 | Tomcat如何实现一键式启停?
08 | Tomcat的“高层们”都负责做什么?
09 | 比较:Jetty架构特点之Connector组件
10 | 比较:Jetty架构特点之Handler组件
11 | 总结:从Tomcat和Jetty中提炼组件化设计规范
12 | 实战:优化并提高Tomcat启动速度
13 | 热点问题答疑(1):如何学习源码?
模块三 连接器 (9讲)
14 | NioEndpoint组件:Tomcat如何实现非阻塞I/O?
15 | Nio2Endpoint组件:Tomcat如何实现异步I/O?
16 | AprEndpoint组件:Tomcat APR提高I/O性能的秘密
17 | Executor组件:Tomcat如何扩展Java线程池?
18 | 新特性:Tomcat如何支持WebSocket?
19 | 比较:Jetty的线程策略EatWhatYouKill
20 | 总结:Tomcat和Jetty中的对象池技术
21 | 总结:Tomcat和Jetty的高性能、高并发之道
22 | 热点问题答疑(2):内核如何阻塞与唤醒进程?
模块四 容器 (8讲)
23 | Host容器:Tomcat如何实现热部署和热加载?
24 | Context容器(上):Tomcat如何打破双亲委托机制?
25 | Context容器(中):Tomcat如何隔离Web应用?
26 | Context容器(下):Tomcat如何实现Servlet规范?
27 | 新特性:Tomcat如何支持异步Servlet?
28 | 新特性:Spring Boot如何使用内嵌式的Tomcat和Jetty?
29 | 比较:Jetty如何实现具有上下文信息的责任链?
30 | 热点问题答疑(3):Spring框架中的设计模式
模块五 通用组件 (4讲)
31 | Logger组件:Tomcat的日志框架及实战
32 | Manager组件:Tomcat的Session管理机制解析
33 | Cluster组件:Tomcat的集群通信原理
特别放送 | 如何持续保持对学习的兴趣?
模块六 性能优化 (8讲)
34 | JVM GC原理及调优的基本思路
35 | 如何监控Tomcat的性能?
36 | Tomcat I/O和线程池的并发调优
37 | Tomcat内存溢出的原因分析及调优
38 | Tomcat拒绝连接原因分析及网络优化
39 | Tomcat进程占用CPU过高怎么办?
40 | 谈谈Jetty性能调优的思路
41 | 热点问题答疑(4): Tomcat和Jetty有哪些不同?
结束语 (1讲)
结束语 | 静下心来,品味经典
深入拆解Tomcat & Jetty
登录|注册

25 | Context容器(中):Tomcat如何隔离Web应用?

李号双 2019-07-06
我在专栏上一期提到,Tomcat 通过自定义类加载器 WebAppClassLoader 打破了双亲委托机制,具体来说就是重写了 JVM 的类加载器 ClassLoader 的 findClass 方法和 loadClass 方法,这样做的目的是优先加载 Web 应用目录下的类。除此之外,你觉得 Tomcat 的类加载器还需要完成哪些需求呢?或者说在设计上还需要考虑哪些方面?
我们知道,Tomcat 作为 Servlet 容器,它负责加载我们的 Servlet 类,此外它还负责加载 Servlet 所依赖的 JAR 包。并且 Tomcat 本身也是也是一个 Java 程序,因此它需要加载自己的类和依赖的 JAR 包。首先让我们思考这一下这几个问题:
假如我们在 Tomcat 中运行了两个 Web 应用程序,两个 Web 应用中有同名的 Servlet,但是功能不同,Tomcat 需要同时加载和管理这两个同名的 Servlet 类,保证它们不会冲突,因此 Web 应用之间的类需要隔离。
假如两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 Spring 的 JAR 包被加载到内存后,Tomcat 要保证这两个 Web 应用能够共享,也就是说 Spring 的 JAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,JVM 的内存会膨胀。
跟 JVM 一样,我们需要隔离 Tomcat 本身的类和 Web 应用的类。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Tomcat & Jetty 》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(19)

  • 王之刚
    最后的问题没有想明白,有人能详细解释一下吗?

    作者回复: 线程上下文加载器其实是线程的一个私有数据,跟线程绑定的,这个线程做完启动Context组件的事情后,会被回收到线程池,之后被用来做其他事情,为了不影响其他事情,需要恢复之前的线程上下文加载器。

    2019-07-06
    1
    11
  • Cy190622

    老师好,您讲个很通透。还有一点问题请教一下:
    1.线程上下文的加载器是不是指定子类加载器来加载具体的某个桥接类。比如JDBC的Driver的加载。
    2.每个Web下面的java类和jar(WEB-INF/classes和WEB-INF/lib),都是WebAppClassLoader加载吗?
    3.Web容器指定的共享目录一般是在什么路径下

    作者回复: 1和2你说的都准确。

    CommonClassLoader对应<Tomcat>/common/*
    CatalinaClassLoader对应 <Tomcat >/server/*
    SharedClassLoader对应 <Tomcat >/shared/*
    WebAppClassloader对应 <Tomcat >/webapps/<app>/WEB-INF/*目录

    2019-07-06
    1
    10
  • nightmare
    老师,上下文加载器是不是比如说我在加载spring的线程设置为webappclassloader那么就算spring的jar是由shared classloader加载的,那么spring加载的过程中也是由webappclassloader来加载,而用完设置回去,是因为我只需要跨classloader的时候才需要线程上下文加载器

    作者回复: 是的👍

    2019-07-06
    4
  • 每天一点点
    课后思考题
    先切换 WebAppClassLoader 是因为 tomcat 的加载机制,需要先加载 web 的类,然后在共享类等
    老师,对么?

    作者回复: 对的

    2019-08-01
    2
  • Li Shunduo
    Tomcat 9中只有这些目录了: conf, logs, bin, lib, temp, work, webapps.
    并没有下面这些类加载器对应的common/shared/server目录,是需要自己手工创建吗?
    CommonClassLoader对应<Tomcat>/common/*
    CatalinaClassLoader对应 <Tomcat >/server/*
    SharedClassLoader对应 <Tomcat >/shared/*
    WebAppClassloader对应 <Tomcat >/webapps/<app>/WEB-INF/*目录

    作者回复: 你可以在Tomcat conf目录下的Catalina.properties文件里配置各种类加载器的加载路径

    2019-07-10
    1
  • 业余爱好者
    之前做了一个项目,在tomcat下面部署了两个springboot打的war,这两个war都依赖了同一个数据访问用的jar包,tomcat在启动第二个war项目时,报什么datasource已经实例化的一个错误,导致第二个项目启动失败。后来查了下资料,在application.yml里禁用了jmx解决。

    虽然问题解决了,但却不明就里,不知道是不是web应用没有做隔离的缘故。不知道这样理解对不对。。

    作者回复: 应该在Tomcat安装目录下建一个shared目录,把web应用共享的库放这个目录下

    2019-07-06
    1
  • z.l
    老师,想到个问题,tomcat加载自身类的时候为什么不能用一个单独的WebAppClassLoader实例来加载,这样不仅不会和web应用的类加载冲突,而且还能把CatalinaClassLoader、CommonClassLoader这俩加载器都省了,简单明了。
    2019-11-23
  • 帽子丨影
    老师好,有个疑问。既然不同的类加载器实例加载的类是不同的,那如果Tomcat给每一个context使用各自的AppClassLoader实例来加载,那不是也可以达到应用隔离的目标了吗

    作者回复: Tomcat正是这样做的

    2019-09-25
  • 无心水
    蓝色部分就是JVM的类加载器分层结构。Tomcat在JVM的基础上又加了三层。线程上下文类加载器,打破了双亲委托机制。
    2019-09-24
  • Demter
    老师,spring的依赖类具体是指哪些啊

    作者回复: 就是你在使用Spring时需要导入的那一堆jar

    2019-08-22
  • 星辰
    老师 我每次看完一篇之后 都很有收获 觉得好兴奋 可能是因为我太激动了 就有点不求甚解了 说真的 很希望老师再出一个专栏 老师再出一个专栏 必定大卖!
    2019-08-21
  • 疯狂咸鱼
    老师,JVM判断两个class实例相不相同,一个是看类加载器,另一个是看全路径名?那么如果全限定名不一样的servlet,为什么不能同时被一个加载器加载?

    作者回复: 两个不同的servlet可以被一个加载器加载

    2019-08-16
  • 今天把springboot打成的多个war包放到tomcat,并且设置了host,但是无法访问到静态资源,而放到Root下就可以,实在搞不懂了

    作者回复: 这好像是Tomcat在代码里写死的逻辑

    2019-07-17
    1
  • 清风
    看代码,CommonClassLoader,CatalinaClassLoader,SharedClassLoader引用了同一个对象,这样的话,是怎么做到类空间隔离的呢

    作者回复: 它们是不同的类加载器实例,实例,实例,不同实例的类加载器加载的同名类是不同的

    2019-07-09
  • 一颗苹果
    老师请问下,如果tomcat的不同应用引用了不同版本的spring依赖,sharedClassloader 怎么区分不同版本呢

    作者回复: 这种情况就不是公共类库了,应该放到各Web应用的路径下去

    2019-07-07
  • 玉芟
    老师,您好:
    我对Thread.currentThread().setContextClassLoader(ClassLoader cl)用法一直有个疑问:
    - setContextClassLoader以后是不是只能显示地通过getContextClassLoader获取ClassLoader后调用loadClass(String name, boolean resolve)方法加载类才能是自定义加载器加载的(验证方法:打印obj.getClass().getClassLoader())?
    - setContextClassLoader以后通过Class.forName(String name)方法等反射得到的类是不是就只能是AppClassLoader加载的?
    我做了个实验:
    自定义类加载器:
    public class DIYClassLoader extends URLClassLoader {
        public DIYClassLoader(URL[] urls) { super(urls); }
        /**
         * 策略很简单:
         * 1)、首先尝试ExtClassLoader|BootstrapClassLoader加载
         * 2)、之后尝试自己加载
         * 3)、最后尝试真正父加载器加载
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> c = findLoadedClass(name);
            ClassLoader parent = getParent();
            if (parent != null) {
                ClassLoader ecl = parent;
                while (ecl.getParent() != null)// 找ExtClassLoader
                    ecl = ecl.getParent();
                try {
                    c = ecl.loadClass(name);
                } catch (ClassNotFoundException e) { }
                if (c == null) {
                    try {
                        c = findClass(name);// DIYClassLoader自己来
                    } catch (ClassNotFoundException e) {}
                    if (c == null) {
                        // 尝试真正父加载器加载,多半是AppClassLoader
                        c = parent.loadClass(name);
                    }
                }
            }else {
                // 直接自己尝试加载
                c = findClass(name);
            }
            if (resolve)
                resolveClass(c);
            return c;
        }
    }
    main方法:
    URL url = Main.class.getClassLoader().getResource(".");
    DIYClassLoader scl = new DIYClassLoader(new URL[] {url});
    Thread.currentThread().setContextClassLoader(scl);
    Class clazz = Class.forName("xx.xx.Xxx");
    // sun.misc.Launcher$AppClassLoader@18b4aac2
    clazz = scl.loadClass("xx.xx.Xxx");
    // xx.xx.DIYClassLoader@682a0b20
    不知道我把问题描述清楚了吗?还望老师解答

    作者回复: 线程上下文加载器本质是线程私有数据,需要显式拿出来,调getContextClassLoader拿

    2019-07-07
  • nightmare
    老师我今天做了试验,在tomcat下和conf同级建立shared目录,然后把两个项目的spring的jar包放到shared目录下,然后webapp/class下的spring的jar包删除,启动报找不到spring的jar包,tomcat版本为7.x,是不是还需要配置什么啊,请老师帮忙指导一下

    作者回复: 你可以在Tomcat conf目录下的catalina.properties文件中配置各加载器的加载路径

    2019-07-06
    1
  • 陆离
    项目有用到切换数据源。
    先创建多个DataSource实例,然后put到AbstractRoutingDataSource的继承类targetDataSources这个map中,最后通过线程的threadlocal带的key值切换。
    这个和今天讲的classloader 有关吗?

    作者回复: 跟线程上下文类加载器一样,threadlocal中的key也是线程私有数据。

    2019-07-06
  • 大漠落木
    找不到 CommonClassLoader CatalinaClassLoader SharedClassLoader 这三个类

    public class WebappClassLoader extends WebappClassLoaderBase

    public abstract class WebappClassLoaderBase extends URLClassLoader

    作者回复: 前面三个是加载器实例名,不是类名,你可以在BootStrap.java中找到

    2019-07-06
收起评论
19
返回
顶部