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)方法;
  • 配置并初始化過濾器;
  • 加載被標(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路徑,若沒有指定則是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());
    }
    // 省略一些代碼
}

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

  • 將過濾器定義和映射添加到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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容