Tomcat&Servlet工作原理之读书笔记

Tomcat是Servlet最受欢迎的容器之一,他们之间彼此依存,为了解耦,通过标准化接口相互协作。

Tomcat的核心组件是ConnectorContainer.其中,Connector组件是可以被替换的,这样就给设计者提供了比较灵活的设计模式,一个Container可以对应多个Connector,它们一起组成了一个Servcie,就可以对外提供服务了。Servcie还要一个生存环境,那就是Server。整个Tomcat的生命周期是由Server控制的。

Tomcat的总体结构.png

Tomcat容器分为4个等级:
Container 容器 ==> Engine容器 ==> Host ==> Context
Tomcat容器模型.png

以Servcie作为“婚姻”

Container:小仙女 Connector:男生
他们是一对快乐的情侣,Connector作为男生,平时主要负责对外交流,Container小仙女主要负责内部的事物,他们两彼此精诚合作,生活也是井井有条。
他们两有了Service这个结婚证呢,就是受外界承认的小夫妻了,无论是在法律上还是生活形式上,他们都是一体的了。


Service接口方法列表.png

为了让生活更加丰富多彩,他们更加细致的规划了自己的生活,就是StandardService,同时实现了Service接口,还实现了Lifecycle接口。

setContainer方法的源码如下:

 @Override
    public void setContainer(Engine engine) {
        Engine oldEngine = this.engine;
        if (oldEngine != null) {
            oldEngine.setService(null);
        }
        this.engine = engine;
        if (this.engine != null) {
            this.engine.setService(this);
        }
        if (getState().isAvailable()) {
            if (this.engine != null) {
                try {
                    this.engine.start();
                } catch (LifecycleException e) {
                    log.warn(sm.getString("standardService.engine.startFailed"), e);
                }
            }
            // Restart MapperListener to pick up new engine.
            try {
                mapperListener.stop();
            } catch (LifecycleException e) {
                log.warn(sm.getString("standardService.mapperListener.stopFailed"), e);
            }
            try {
                mapperListener.start();
            } catch (LifecycleException e) {
                log.warn(sm.getString("standardService.mapperListener.startFailed"), e);
            }
            if (oldEngine != null) {
                try {
                    oldEngine.stop();
                } catch (LifecycleException e) {
                    log.warn(sm.getString("standardService.engine.stopFailed"), e);
                }
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("container", oldEngine, this.engine);
    }

这段代码逻辑很简单,首先判断当前这个Service有没有关联Container,如果已经有关联,就去掉——>oldEngine.setService(null);如果这个Container已经启动了,则结束它的生命周期——>oldEngine.stop();然后再启动新的关联、初始化并开始这个新的Container得生命周期。
addContainer方法的源码如下:

@Override
    public void addConnector(Connector connector) {
        synchronized (connectorsLock) {
            connector.setService(this);  //设置关联关系
            Connector results[] = new Connector[connectors.length + 1]; //初始化工作
            System.arraycopy(connectors, 0, results, 0, connectors.length);
            results[connectors.length] = connector;
            connectors = results;
            if (getState().isAvailable()) {
                try {
                    connector.start();
                } catch (LifecycleException e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
            // Report this property change to interested listeners
            support.firePropertyChange("connector", null, connector);
        }
    }

以Server为“居”

既然Container小仙女和Connector有了合法的夫妻关系,那么他们也需要一个基本的条件去维系这段“婚姻”,他们需要一个实体的家,Server.
Server要完成的任务很简答,就是提高一个接口让其它程序可以访问这个Service集合,同时也要维护他所包含的Service的生命周期。
StandardServer是他的标准实现类,同时也继承了LifecycleMBeanBase。
addService方法源码如下:

@Override
    public void addService(Service service) {
        service.setServer(this);  //由此可以看出Service和Server是相互关联的
        synchronized (servicesLock) {
            Service results[] = new Service[services.length + 1];
            System.arraycopy(services, 0, results, 0, services.length);
            results[services.length] = service;
            services = results;
            if (getState().isAvailable()) {
                try {
                    service.start();
                } catch (LifecycleException e) {
                    // Ignore
                }
            }
            // Report this property change to interested listeners
            support.firePropertyChange("service", null, service);
        }
    }

组件的生命线“Lifecycle”

Tomcat中组件的生命周期都是由Lifecycle接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有他的组件控制了。这样一层一层到最高级的组件就可以控制Tomcat中所有组件的生命周期了,这个最高级的组件就是Server,控制Server的是Startup,也就是启动和关闭Tomcat.


Lifecycle接口方法.png

除了控制生命周期的start和stop方法外,还有一个监听机制,在生命周期开始和结束时做一些额外的操作。
Lifecycle接口实现都在其它组件中。

负责外部交流的Connector

接受浏览器发过来的TCP请求,创建一个Request和Response对象分别用于请求端交换数据,然后将产生一个线程来处理这个请求并把产生的Request和Response对象传给处理这个请求的线程,之后处理这个线程就是Container的事情了。

Servlet容器Container

Container是容器的父接口,所有容器必须实现这个接口,设计是典型的责任链的设计模式,他有4个容器组成,分别是Engine、Context和Wrapper,这四个组件不是平行的,而是父子关系。通常一个Servlet class对应一个Wrapper.

Engine容器

标准实现类时StandardEngine,Engine没有父容器了,调用setParent就会出错

 @Override
    public void setParent(Container container) {

        throw new IllegalArgumentException
            (sm.getString("standardEngine.notParent"));

    }

添加子容器也只能是Host类型

 @Override
    public void addChild(Container child) {

        if (!(child instanceof Host))
            throw new IllegalArgumentException
                (sm.getString("standardEngine.notHost"));
        super.addChild(child);

    }

Host容器

Host是Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,而且进行标识以便于区分。他的子容器是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。

Context容器

他具备Servlet运行的基本环境,是在管理Servlet实例,Servlet实例在Context容器中是以Wrapper出现的。

Wrapper容器

Wrapper代表一个Servlet,包括Servlet的装载、初始化、执行和资源回收。是最底层的,没有再底层的容器了。
loadServlet是个很重要的方法,代码片段如下:

public synchronized Servlet loadServlet() throws ServletException {
     ······
        Servlet servlet;
        try {
            long t1=System.currentTimeMillis();
            // Complain if no servlet class has been specified
            if (servletClass == null) {
                unavailable(null);
                throw new ServletException
                    (sm.getString("standardWrapper.notClass", getName()));
            }

            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {
                unavailable(null);
                // Restore the context ClassLoader
                throw new ServletException
                    (sm.getString("standardWrapper.notServlet", servletClass), e);
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                unavailable(null);
                if(log.isDebugEnabled()) {
                    log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
                }

                // Restore the context ClassLoader
                throw new ServletException
                    (sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            if (multipartConfigElement == null) {
                MultipartConfig annotation =
                        servlet.getClass().getAnnotation(MultipartConfig.class);
                if (annotation != null) {
                    multipartConfigElement =
                            new MultipartConfigElement(annotation);
                }
            }

            processServletSecurityAnnotation(servlet.getClass());

            // Special handling for ContainerServlet instances
            if ((servlet instanceof ContainerServlet) &&
                    (isContainerProvidedServlet(servletClass) ||
                            ((Context) getParent()).getPrivileged() )) {
                ((ContainerServlet) servlet).setWrapper(this);
            }

            classLoadTime=(int) (System.currentTimeMillis() -t1);

            if (servlet instanceof SingleThreadModel) {
                if (instancePool == null) {
                    instancePool = new Stack<>();
                }
                singleThreadModel = true;
            }

            initServlet(servlet);

            fireContainerEvent("load", this);

            loadTime=System.currentTimeMillis() -t1;
        } finally {
            if (swallowOutput) {
                String log = SystemLogHandler.stopCapture();
                if (log != null && log.length() > 0) {
                    if (getServletContext() != null) {
                        getServletContext().log(log);
                    } else {
                        out.println(log);
                    }
                }
            }
        }
        return servlet;
    }

Servlet容器的工作原理

Servlet顶层类关联图.png

由上图可知,Servlet规范就是基于上面的几个类运转的,与Servlet主动关联的是三个类:ServletConfig、ServletRequest、ServletResponse,这3个类都是通过容器传递给Servlet的。
用户从浏览器向服务器发起一个请求通常会包含如下信息:
http://hostname:port/contextpath/servletpath
hostname:port用来与服务器建立TCP连接,而后面的URL才用来选择服务器的哪个子容器服务用户的请求。
这种映射关系专门由一个类来完成,这个类就是org.apache.servlet.util.http.mapper,这个类保存了Tomcat的Container所有子容器的信息。

  • 创建一个Context容器,很重要的一个配置是ContextConfig,负责整个Web应用的配置文件解析工作。
  • Context init(一个Context对应一个web),Context容器的Listener将被调用,ContextConfig调用了LifecycleListener接口。
  • Context执行startInternal方法
  • Web应用的初始化工作,ContextConfig中的configureStart方法实现。
  • 创建Servlet对象
  • 初始化Servlet

Servlet中的url-pattern

匹配顺序:精确匹配 路径匹配 后缀匹配

程序媛小白一枚,如有错误,烦请批评指正!(#.#)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容