Tomcat啟動(dòng)分析(十) - Context組件

Context組件表示一個(gè)Web應(yīng)用朦促,運(yùn)行于一個(gè)特定的虛擬主機(jī)Host中膝晾。每個(gè)Web應(yīng)用可以基于WAR文件,也可以基于相應(yīng)的未打包目錄思灰。Catalina根據(jù)HTTP請(qǐng)求URI基于最長(zhǎng)匹配選擇處理該請(qǐng)求的Web應(yīng)用玷犹,一旦選擇混滔,該Context就會(huì)根據(jù)定義好的servlet映射選擇一個(gè)適當(dāng)?shù)膕ervlet去處理洒疚。

Context組件

Context接口繼承Container接口,StandardContext類是Container組件的默認(rèn)實(shí)現(xiàn)坯屿,它繼承ContainerBase基類并實(shí)現(xiàn)了Context接口油湖,其構(gòu)造函數(shù)和部分成員變量如下所示:

public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
    private static final Log log = LogFactory.getLog(StandardContext.class);
    // ----------------------------------------------------------- Constructors
    /**
     * Create a new StandardContext component with the default basic Valve.
     */
    public StandardContext() {
        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        // Set defaults
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            // Strict servlet compliance requires all extension mapped servlets
            // to be checked against welcome files
            resourceOnlyServlets.add("jsp");
        }
    }
    // ----------------------------------------------------- Instance Variables
    /**
     * Allow multipart/form-data requests to be parsed even when the
     * target servlet doesn't specify @MultipartConfig or have a
     * <multipart-config> element.
     */
    protected boolean allowCasualMultipartParsing = false;

    private boolean swallowAbortedUploads = true;
    private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
        new LinkedHashMap<>();
    private URL configFile = null;
    private boolean configured = false;
    protected ApplicationContext context = null;
    private String encodedPath = null;
    private String path = null;
    private String docBase = null;
    private boolean reloadable = false;
    private boolean unpackWAR = true;
    private boolean copyXML = false;
    private boolean override = false;
    private boolean swallowOutput = false;

    // 省略一些代碼

    private boolean validateClientProvidedNewSessionId = true;
    private boolean mapperContextRootRedirectEnabled = true;
    private boolean mapperDirectoryRedirectEnabled = false;
    private boolean useRelativeRedirects = !Globals.STRICT_SERVLET_COMPLIANCE;
    private boolean dispatchersUseEncodedPaths = true;
    private String requestEncoding = null;
    private String responseEncoding = null;
    private boolean allowMultipleLeadingForwardSlashInPath = false;
}
  • 成員變量的含義可以參考Context的配置文檔
  • Context組件的構(gòu)造函數(shù)為自己的Pipeline添加了基本閥StandardContextValve领跛,addChild方法只能添加Wrapper組件乏德。

組件初始化

StandardContext類的initInternal方法如下:

@Override
protected void initInternal() throws LifecycleException {
    super.initInternal();

    // Register the naming resources
    if (namingResources != null) {
        namingResources.init();
    }

    // Send j2ee.object.created notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.object.created",
                this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }
}
  • 調(diào)用基類ContainerBase的對(duì)應(yīng)方法為自己創(chuàng)建線程池;
  • 命名服務(wù)初始化吠昭;
  • 發(fā)送JMX的j2ee.object.created通知喊括。

組件啟動(dòng)

由于StandardContext類的startInternal方法代碼較多,故此處省略矢棚。簡(jiǎn)而言之郑什,Context組件啟動(dòng)時(shí)按順序做了如下工作:

  • 為該Context創(chuàng)建工作目錄,如${catalina.base}/work/Engine名稱/Host名稱/Context的基本名稱蒲肋;
  • 創(chuàng)建WebResourceRoot用于讀取Web應(yīng)用的資源蘑拯;
  • 創(chuàng)建Loader,Loader是ClassLoader的一種實(shí)現(xiàn)兜粘,可以按需重新加載申窘;
  • 初始化字符集映射;
  • 啟動(dòng)Loader孔轴;
  • 發(fā)布事件Lifecycle.CONFIGURE_START_EVENT剃法;
  • 啟動(dòng)子容器組件(即Wrapper組件);
  • 執(zhí)行各ServletContainerInitializer回調(diào)方法路鹰;
  • 配置并初始化過(guò)濾器玄窝;
  • 加載被標(biāo)識(shí)為“啟動(dòng)加載”的servlet牵寺;
  • 發(fā)布事件Lifecycle.START_EVENT。

ContextConfig監(jiān)聽器

Context組件里比較重要的生命周期事件監(jiān)聽器之一是ContextConfig監(jiān)聽器恩脂,Tomcat在解析server.xml時(shí)為Digester添加了ContextRuleSet規(guī)則帽氓,進(jìn)而為StandardContext添加ContextConfig生命周期監(jiān)聽器(請(qǐng)參見本系列的Tomcat啟動(dòng)分析(二))。ContextConfig的主要作用是在Context組件啟動(dòng)時(shí)響應(yīng)Context發(fā)布的事件俩块。

成員變量

ContextConfig監(jiān)聽器重要的部分成員變量如下:

protected Context context = null;
protected String defaultWebXml = null;
protected boolean ok = false;
protected String originalDocBase = null;
protected final Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap =
        new LinkedHashMap<>();
protected final Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap =
        new HashMap<>();
  • context變量引用與之關(guān)聯(lián)的Context組件黎休;
  • defaultWebXml變量表示默認(rèn)的web.xml路徑,若沒(méi)有指定則是conf/web.xml玉凯;
  • ok變量表示配置和啟動(dòng)階段是否正常势腮;
  • originalDocBase變量表示Context的docBase;
  • initializerClassMap和typeInitializerMap變量都與ServletContainerInitializer有關(guān)漫仆。

響應(yīng)生命周期事件

ContextConfig類實(shí)現(xiàn)的lifecycleEvent方法如下:

@Override
public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        // Restore docBase for management tools
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }
}
  • 首先將發(fā)布事件的Context組件保存到context變量捎拯;
  • 然后針對(duì)不同事件執(zhí)行不同的事件處理方法,比較重要的是Lifecycle.AFTER_INIT_EVENT和Lifecycle.CONFIGURE_START_EVENT兩個(gè)事件盲厌。

初始化后事件

回憶LifecycleBase類的init方法署照,在調(diào)用initInternal方法后會(huì)調(diào)用setStateInternal更改組件的狀態(tài)并發(fā)布LifecycleState.INITIALIZED枚舉對(duì)應(yīng)的事件,即初始化后事件Lifecycle.AFTER_INIT_EVENT吗浩。由于容器組件均繼承自LifecycleBase類建芙,因此StandardContext在執(zhí)行initInternal方法后也會(huì)發(fā)布該事件,ContextConfig監(jiān)聽器對(duì)此事件的響應(yīng)是執(zhí)行init方法懂扼,該方法代碼如下:

protected void init() {
    // Called from StandardContext.init()

    Digester contextDigester = createContextDigester();
    contextDigester.getParser();

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.init"));
    }
    context.setConfigured(false);
    ok = true;

    contextConfig(contextDigester);
}
  • 首先創(chuàng)建了Digester對(duì)象禁荸;
  • contextConfig方法利用上述Digester對(duì)象解析<Context>配置,參見Context配置文檔的Defining a context一節(jié)阀湿。

配置開始事件

StandardContext在啟動(dòng)時(shí)會(huì)發(fā)布配置開始事件Lifecycle.CONFIGURE_START_EVENT赶熟,ContextConfig監(jiān)聽器對(duì)此事件的響應(yīng)是執(zhí)行configureStart方法,該方法代碼如下:

protected synchronized void configureStart() {
    // Called from StandardContext.start()

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.start"));
    }

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.xmlSettings",
                context.getName(),
                Boolean.valueOf(context.getXmlValidation()),
                Boolean.valueOf(context.getXmlNamespaceAware())));
    }

    webConfig();

    if (!context.getIgnoreAnnotations()) {
        applicationAnnotationsConfig();
    }
    if (ok) {
        validateSecurityRoles();
    }

    // Configure an authenticator if we need one
    if (ok) {
        authenticatorConfig();
    }

    // Dump the contents of this pipeline if requested
    if (log.isDebugEnabled()) {
        log.debug("Pipeline Configuration:");
        Pipeline pipeline = context.getPipeline();
        Valve valves[] = null;
        if (pipeline != null) {
            valves = pipeline.getValves();
        }
        if (valves != null) {
            for (int i = 0; i < valves.length; i++) {
                log.debug("  " + valves[i].getClass().getName());
            }
        }
        log.debug("======================");
    }

    // Make our application available if no problems were encountered
    if (ok) {
        context.setConfigured(true);
    } else {
        log.error(sm.getString("contextConfig.unavailable"));
        context.setConfigured(false);
    }
}

webConfig方法根據(jù)Servlet規(guī)范解析web.xml:

  • 先找出Web應(yīng)用jar內(nèi)的web-fragment.xml并把它們排序陷嘴;
  • 查找ServletContainerInitializer接口的實(shí)現(xiàn)類映砖;
  • 處理/WEB-INF/classes下Web資源內(nèi)的注解,如@HandlesTypes罩旋、@WebServlet啊央、@WebFilter和@WebListener等;
  • 處理jar內(nèi)的注解涨醋,如@HandlesTypes瓜饥、@WebServlet、@WebFilter和@WebListener等浴骂;
  • 將web-fragment.xml和默認(rèn)web-fragment.xml合并到web.xml乓土;
  • configureContext方法利用合并后的web.xml配置Context組件。

configureContext方法部分代碼如下:

private void configureContext(WebXml webxml) {
    // As far as possible, process in alphabetical order so it is easy to
    // check everything is present
    // Some validation depends on correct public ID
    context.setPublicId(webxml.getPublicId());

    // Everything else in order
    context.setEffectiveMajorVersion(webxml.getMajorVersion());
    context.setEffectiveMinorVersion(webxml.getMinorVersion());

    for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
        context.addParameter(entry.getKey(), entry.getValue());
    }
    // 省略一些代碼
    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }
    
    // 省略一些代碼
    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        // Description is ignored
        // Display name is ignored
        // Icons are ignored

        // jsp-file gets passed to the JSP Servlet as an init-param

        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        if (servlet.getEnabled() != null) {
            wrapper.setEnabled(servlet.getEnabled().booleanValue());
        }
        wrapper.setName(servlet.getServletName());
        Map<String,String> params = servlet.getParameterMap();
        for (Entry<String, String> entry : params.entrySet()) {
            wrapper.addInitParameter(entry.getKey(), entry.getValue());
        }
        wrapper.setRunAs(servlet.getRunAs());
        Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
        for (SecurityRoleRef roleRef : roleRefs) {
            wrapper.addSecurityReference(
                    roleRef.getName(), roleRef.getLink());
        }
        wrapper.setServletClass(servlet.getServletClass());
        MultipartDef multipartdef = servlet.getMultipartDef();
        if (multipartdef != null) {
            if (multipartdef.getMaxFileSize() != null &&
                    multipartdef.getMaxRequestSize()!= null &&
                    multipartdef.getFileSizeThreshold() != null) {
                wrapper.setMultipartConfigElement(new MultipartConfigElement(
                        multipartdef.getLocation(),
                        Long.parseLong(multipartdef.getMaxFileSize()),
                        Long.parseLong(multipartdef.getMaxRequestSize()),
                        Integer.parseInt(
                                multipartdef.getFileSizeThreshold())));
            } else {
                wrapper.setMultipartConfigElement(new MultipartConfigElement(
                        multipartdef.getLocation()));
            }
        }
        if (servlet.getAsyncSupported() != null) {
            wrapper.setAsyncSupported(
                    servlet.getAsyncSupported().booleanValue());
        }
        wrapper.setOverridable(servlet.isOverridable());
        context.addChild(wrapper);
    }
    for (Entry<String, String> entry :
            webxml.getServletMappings().entrySet()) {
        context.addServletMappingDecoded(entry.getKey(), entry.getValue());
    }
    // 省略一些代碼
}

該方法做了很多工作,例如:

  • 將過(guò)濾器定義和映射添加到Context組件趣苏;
  • 對(duì)每個(gè)servlet定義狡相,調(diào)用Context組件的createWrapper方法將servlet定義和參數(shù)包裝成Wrapper,然后將其添加到Context組件中食磕;
  • 將servlet映射添加到Context組件尽棕;
  • ...

參考文獻(xiàn)

http://cmsblogs.com/?p=2672

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彬伦,隨后出現(xiàn)的幾起案子滔悉,更是在濱河造成了極大的恐慌,老刑警劉巖单绑,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件回官,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡搂橙,警方通過(guò)查閱死者的電腦和手機(jī)歉提,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門区转,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜗帜,“玉大人恋拷,你說(shuō)我怎么就攤上這事厅缺⊙绯ィ” “怎么了湘捎?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵窄刘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我活翩,道長(zhǎng)材泄,這世上最難降的妖魔是什么吨岭? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任旦事,我火速辦了婚禮姐浮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘埋凯。我一直安慰自己白对,他們只是感情好换怖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布沉颂。 她就那樣靜靜地躺著铸屉,像睡著了一般彻坛。 火紅的嫁衣襯著肌膚如雪昌屉。 梳的紋絲不亂的頭發(fā)上间驮,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天竞帽,我揣著相機(jī)與錄音屹篓,去河邊找鬼抱虐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灶轰,可吹牛的內(nèi)容都是我干的刷钢。 我是一名探鬼主播内地,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼非凌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敞嗡!你這毒婦竟也來(lái)了喉悴?” 一聲冷哼從身側(cè)響起玖媚,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤勺像,失蹤者是張志新(化名)和其女友劉穎咏删,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體激挪,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垄分,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豺瘤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坐求。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桥嗤,死狀恐怖泛领,靈堂內(nèi)的尸體忽然破棺而出渊鞋,到底是詐尸還是另有隱情篓像,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站丹皱,受9級(jí)特大地震影響摊崭,放射性物質(zhì)發(fā)生泄漏呢簸。R本人自食惡果不足惜乏屯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一蛤迎、第九天 我趴在偏房一處隱蔽的房頂上張望替裆。 院中可真熱鬧,春花似錦召川、人聲如沸荧呐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)概耻。三九已至罐呼,卻和暖如春嫉柴,著一層夾襖步出監(jiān)牢的瞬間计螺,已是汗流浹背登馒。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工陈轿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留济欢,地道東北人法褥。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓揍愁,卻偏偏與公主長(zhǎng)得像杀饵,于是被迫代替她去往敵國(guó)和親切距。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谜悟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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