Tomcat&Servlet工作原理之讀書筆記

Tomcat是Servlet最受歡迎的容器之一愈捅,他們之間彼此依存咧最,為了解耦,通過標(biāo)準(zhǔn)化接口相互協(xié)作阎肝。

Tomcat的核心組件是ConnectorContainer.其中挤渔,Connector組件是可以被替換的,這樣就給設(shè)計者提供了比較靈活的設(shè)計模式风题,一個Container可以對應(yīng)多個Connector判导,它們一起組成了一個Servcie,就可以對外提供服務(wù)了沛硅。Servcie還要一個生存環(huán)境眼刃,那就是Server。整個Tomcat的生命周期是由Server控制的摇肌。

Tomcat的總體結(jié)構(gòu).png

Tomcat容器分為4個等級:
Container 容器 ==> Engine容器 ==> Host ==> Context
Tomcat容器模型.png

以Servcie作為“婚姻”

Container:小仙女 Connector:男生
他們是一對快樂的情侶擂红,Connector作為男生,平時主要負(fù)責(zé)對外交流围小,Container小仙女主要負(fù)責(zé)內(nèi)部的事物昵骤,他們兩彼此精誠合作,生活也是井井有條肯适。
他們兩有了Service這個結(jié)婚證呢变秦,就是受外界承認(rèn)的小夫妻了,無論是在法律上還是生活形式上框舔,他們都是一體的了蹦玫。


Service接口方法列表.png

為了讓生活更加豐富多彩,他們更加細(xì)致的規(guī)劃了自己的生活刘绣,就是StandardService,同時實現(xiàn)了Service接口樱溉,還實現(xiàn)了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);
    }

這段代碼邏輯很簡單纬凤,首先判斷當(dāng)前這個Service有沒有關(guān)聯(lián)Container福贞,如果已經(jīng)有關(guān)聯(lián),就去掉——>oldEngine.setService(null);如果這個Container已經(jīng)啟動了停士,則結(jié)束它的生命周期——>oldEngine.stop();然后再啟動新的關(guān)聯(lián)肚医、初始化并開始這個新的Container得生命周期绢馍。
addContainer方法的源碼如下:

@Override
    public void addConnector(Connector connector) {
        synchronized (connectorsLock) {
            connector.setService(this);  //設(shè)置關(guān)聯(lián)關(guān)系
            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有了合法的夫妻關(guān)系向瓷,那么他們也需要一個基本的條件去維系這段“婚姻”肠套,他們需要一個實體的家,Server.
Server要完成的任務(wù)很簡答猖任,就是提高一個接口讓其它程序可以訪問這個Service集合你稚,同時也要維護他所包含的Service的生命周期。
StandardServer是他的標(biāo)準(zhǔn)實現(xiàn)類朱躺,同時也繼承了LifecycleMBeanBase刁赖。
addService方法源碼如下:

@Override
    public void addService(Service service) {
        service.setServer(this);  //由此可以看出Service和Server是相互關(guān)聯(lián)的
        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接口來控制的,組件只要繼承這個接口并實現(xiàn)其中的方法就可以統(tǒng)一被擁有他的組件控制了长搀。這樣一層一層到最高級的組件就可以控制Tomcat中所有組件的生命周期了宇弛,這個最高級的組件就是Server,控制Server的是Startup,也就是啟動和關(guān)閉Tomcat.


Lifecycle接口方法.png

除了控制生命周期的start和stop方法外,還有一個監(jiān)聽機制源请,在生命周期開始和結(jié)束時做一些額外的操作枪芒。
Lifecycle接口實現(xiàn)都在其它組件中。

負(fù)責(zé)外部交流的Connector

接受瀏覽器發(fā)過來的TCP請求谁尸,創(chuàng)建一個Request和Response對象分別用于請求端交換數(shù)據(jù)舅踪,然后將產(chǎn)生一個線程來處理這個請求并把產(chǎn)生的Request和Response對象傳給處理這個請求的線程,之后處理這個線程就是Container的事情了良蛮。

Servlet容器Container

Container是容器的父接口抽碌,所有容器必須實現(xiàn)這個接口,設(shè)計是典型的責(zé)任鏈的設(shè)計模式决瞳,他有4個容器組成货徙,分別是Engine、Context和Wrapper,這四個組件不是平行的皮胡,而是父子關(guān)系痴颊。通常一個Servlet class對應(yīng)一個Wrapper.

Engine容器

標(biāo)準(zhǔn)實現(xiàn)類時StandardEngine,Engine沒有父容器了胸囱,調(diào)用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中代表一個虛擬主機,這個虛擬主機的作用就是運行多個應(yīng)用烹笔,它負(fù)責(zé)安裝和展開這些應(yīng)用裳扯,而且進行標(biāo)識以便于區(qū)分。他的子容器是Context,它除了關(guān)聯(lián)子容器外谤职,還有就是保存一個主機應(yīng)該有的信息饰豺。

Context容器

他具備Servlet運行的基本環(huán)境,是在管理Servlet實例允蜈,Servlet實例在Context容器中是以Wrapper出現(xiàn)的冤吨。

Wrapper容器

Wrapper代表一個Servlet蒿柳,包括Servlet的裝載、初始化漩蟆、執(zhí)行和資源回收垒探。是最底層的,沒有再底層的容器了怠李。
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頂層類關(guān)聯(lián)圖.png

由上圖可知,Servlet規(guī)范就是基于上面的幾個類運轉(zhuǎn)的捺癞,與Servlet主動關(guān)聯(lián)的是三個類:ServletConfig夷蚊、ServletRequest、ServletResponse,這3個類都是通過容器傳遞給Servlet的髓介。
用戶從瀏覽器向服務(wù)器發(fā)起一個請求通常會包含如下信息:
http://hostname:port/contextpath/servletpath
hostname:port用來與服務(wù)器建立TCP連接惕鼓,而后面的URL才用來選擇服務(wù)器的哪個子容器服務(wù)用戶的請求。
這種映射關(guān)系專門由一個類來完成唐础,這個類就是org.apache.servlet.util.http.mapper,這個類保存了Tomcat的Container所有子容器的信息箱歧。

  • 創(chuàng)建一個Context容器,很重要的一個配置是ContextConfig彻犁,負(fù)責(zé)整個Web應(yīng)用的配置文件解析工作叫胁。
  • Context init(一個Context對應(yīng)一個web),Context容器的Listener將被調(diào)用,ContextConfig調(diào)用了LifecycleListener接口汞幢。
  • Context執(zhí)行startInternal方法
  • Web應(yīng)用的初始化工作驼鹅,ContextConfig中的configureStart方法實現(xiàn)。
  • 創(chuàng)建Servlet對象
  • 初始化Servlet

Servlet中的url-pattern

匹配順序:精確匹配 路徑匹配 后綴匹配

程序媛小白一枚森篷,如有錯誤输钩,煩請批評指正!(#.#)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仲智,一起剝皮案震驚了整個濱河市买乃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钓辆,老刑警劉巖剪验,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異前联,居然都是意外死亡功戚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門似嗤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啸臀,“玉大人,你說我怎么就攤上這事烁落〕肆#” “怎么了豌注?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灯萍。 經(jīng)常有香客問我轧铁,道長,這世上最難降的妖魔是什么竟稳? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任属桦,我火速辦了婚禮,結(jié)果婚禮上他爸,老公的妹妹穿的比我還像新娘。我一直安慰自己果善,他們只是感情好诊笤,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巾陕,像睡著了一般讨跟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鄙煤,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天晾匠,我揣著相機與錄音,去河邊找鬼梯刚。 笑死凉馆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亡资。 我是一名探鬼主播澜共,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锥腻!你這毒婦竟也來了嗦董?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤瘦黑,失蹤者是張志新(化名)和其女友劉穎京革,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幸斥,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡匹摇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了睡毒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片来惧。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖演顾,靈堂內(nèi)的尸體忽然破棺而出供搀,到底是詐尸還是另有隱情隅居,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布葛虐,位于F島的核電站胎源,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屿脐。R本人自食惡果不足惜涕蚤,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望的诵。 院中可真熱鬧万栅,春花似錦、人聲如沸西疤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽代赁。三九已至扰她,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芭碍,已是汗流浹背徒役。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窖壕,地道東北人忧勿。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像艇拍,于是被迫代替她去往敵國和親狐蜕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容