Tomcat源碼解析-容器組件之StandardHost

1 Container容器

Container容器用來表示tomcat中servlet容器,負(fù)責(zé)servelt的加載和管理浆劲,處理請求ServletRequest,并返回標(biāo)準(zhǔn)的 ServletResponse 對象給連接器。

image

Container容器組件

tomcat 將Container容器按功能分為4個組件,分別是 Engine、Host温技、Context 和 Wrapper。這 4 種容器不是平行關(guān)系扭粱,而是父子關(guān)系舵鳞。

  • Wrapper:表示一個 Servlet

  • Context:表示一個 Web 應(yīng)用程序,一個 Web 應(yīng)用程序中可能會有多個 Servlet

  • Host:表示的是一個虛擬主機(jī)琢蛤,或者說一個站點蜓堕,可以給 Tomcat 配置多個虛擬主機(jī)地址,而一個虛擬主機(jī)下可以部署多個 Web 應(yīng)用程序

  • Engine:表示引擎博其,用來管理多個虛擬站點套才,一個 Service 最多只能有一個 Engine。

image

2 StandardHost類結(jié)構(gòu)

image
2.1 Lifecycle接口
public interface Lifecycle {
    ....
    // 初始化方法
    public void init() throws LifecycleException;
    // 啟動方法
    public void start() throws LifecycleException;
    // 停止方法慕淡,和start對應(yīng)
    public void stop() throws LifecycleException;
    // 銷毀方法背伴,和init對應(yīng)
    public void destroy() throws LifecycleException;
    // 獲取生命周期狀態(tài)
    public LifecycleState getState();
    // 獲取字符串類型的生命周期狀態(tài)
    public String getStateName();
}

Lifecycle接口定義tomcat中所有組件的生命周期相關(guān)接口方法。Tomcat 定義一個基類LifecycleBase 來實現(xiàn) Lifecycle 接口峰髓,把一些公共的邏輯放到基類中實現(xiàn)傻寂。而子類就負(fù)責(zé)實現(xiàn)自己的初始化、啟動和停止等模板方法携兵。

詳見 Tomcat架構(gòu)設(shè)計-組件生命周期 Lifecycle

2.2 Container接口
public interface Container extends Lifecycle {
    
    public static final String ADD_CHILD_EVENT = "addChild";

    public static final String ADD_VALVE_EVENT = "addValve";

    public static final String REMOVE_CHILD_EVENT = "removeChild";
    
    public static final String REMOVE_VALVE_EVENT = "removeValve";
    
    //返回日志組件
    public Log getLogger();
    
    //返回日志名稱
    public String getLogName();
    
    //返回容器注冊到JMX bean ObjectName
    public ObjectName getObjectName();
    
    //返回容器注冊到JMX bean 命名空間
    public String getDomain();
    
    //返回容器注冊到JMX bean 屬性
    public String getMBeanKeyProperties();
    
    //返回容器依賴Pipeline組件
    public Pipeline getPipeline();
    
    //返回容器依賴Cluster組件
    public Cluster getCluster();
    
    //設(shè)置容器依賴Cluster組件
    public void setCluster(Cluster cluster);
    
    //返回周期性任務(wù)執(zhí)行間隔事件
    public int getBackgroundProcessorDelay();
    
    //設(shè)置周期性任務(wù)執(zhí)行間隔事件
    public void setBackgroundProcessorDelay(int delay);
    
    //返回容器名稱
    public String getName();
    
    //設(shè)置容器名稱
    public void setName(String name);
    
    //返回父容器
    public Container getParent();
    
    //設(shè)置父容器
    public void setParent(Container container);
    
    //返回父類加載器
    public ClassLoader getParentClassLoader();
    
    //設(shè)置父類加載器
    public void setParentClassLoader(ClassLoader parent);
    
    //返回容器依賴Realm組件
    public Realm getRealm();
    
    // 設(shè)置容器依賴Realm組件
    public void setRealm(Realm realm);
    
    //容器默認(rèn)周期性任務(wù)處理調(diào)用方法
    public void backgroundProcess();
    
    //為當(dāng)前容器組件添加子容器組件
    public void addChild(Container child);
    
    //添加容器事件監(jiān)聽器
    public void addContainerListener(ContainerListener listener);
    
    //添加屬性變更監(jiān)聽器
    public void addPropertyChangeListener(PropertyChangeListener listener);
    
    //查找指定名稱的子容器
    public Container findChild(String name);
    
    //獲取所有子容器組件
    public Container[] findChildren();
    
    //返回所有容器事件監(jiān)聽器
    public ContainerListener[] findContainerListeners();

    //刪除子容器
    public void removeChild(Container child);
    
    //當(dāng)前容器刪除容器事件監(jiān)聽器
    public void removeContainerListener(ContainerListener listener);
    
    //當(dāng)前容器刪除屬性變更監(jiān)聽器
    public void removePropertyChangeListener(PropertyChangeListener listener);
    
    //處理容器事件
    public void fireContainerEvent(String type, Object data);
    
    //使用AccessLog組件打印請求日志
    public void logAccess(Request request, Response response, long time,
            boolean useDefault);
    
    //返回訪問日志組件AccessLog
    public AccessLog getAccessLog();
    
    //返回設(shè)置處理子容器啟動關(guān)閉線程池核心線程數(shù)疾掰。
    public int getStartStopThreads();

    //設(shè)置處理子容器啟動關(guān)閉線程池核心線程數(shù)。
    public void setStartStopThreads(int startStopThreads);

    //返回tomcat工作目錄
    public File getCatalinaBase();

    //返回tomcat安裝目錄
    public File getCatalinaHome();
}

Container接口定義tomcat中所有容器組件的通用接口方法徐紧。Tomcat 定義一個基類ContainerBase 來實現(xiàn)Container 接口静檬,把一些公共的邏輯放到基類中實現(xiàn)炭懊。

詳見 Tomcat架構(gòu)設(shè)計-容器組件基類 ContainerBase

3 Tomcat中虛擬主機(jī)Host

在tomcat中最核心功能就是將一個靜態(tài)資源目錄或一個應(yīng)用程序部署到容器中。而這個容器就是指得Host容器組件拂檩。而靜態(tài)資源或一個應(yīng)用程序通過Context容器組件來表示侮腹。所謂部署就是加載到Host容器的子組件中。當(dāng)然虛擬主機(jī)除了部署外還又其他功能广恢,包括熱部署凯旋,懶加載呀潭,別名等钉迷。

3.1 部署靜態(tài)資源

如果想要將一個靜態(tài)資源目錄部署到Tomcat服務(wù)器上,tomcat提供了多種部署方式

在server.xml中配置

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true" copyXML="true" xmlBase="conf\Catalina\localhost">
         <Context path="/JavaWebApp" docBase="F:\JavaWebDemoProject" />
 </Host>

path表示Context根路徑钠署,docBase表示映射靜態(tài)資源目錄

在xmlBase路徑下配置xml文件

在$CATALINA_BASE/xmlBase 路徑下創(chuàng)建 JavaWebApp.xml糠聪,xmlBase配置在Host標(biāo)簽屬性中

<Context docBase="F:\JavaWebDemoProject" />

文件名稱表示Context根路徑,docBase表示映射靜態(tài)資源目錄

將資源文件拷貝到appBase路徑下

appBase路徑在Host標(biāo)簽屬性中定義,文件名稱表示Context根路徑谐鼎。

3.2 部署應(yīng)用程序

部署應(yīng)用程序到appBase目錄

appBase是在server.xml文件Host標(biāo)簽appBase屬性來定義舰蟆,appBase可以填寫相對路徑或者絕對路徑,如果是相對路徑那么完整路徑為CATALINA_BASE/appBase,這里CATALINA_BASE表示tomcat的工作目錄

 <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true" copyXML="true" xmlBase="conf\Catalina\localhost">
3.3 懶部署

虛擬主機(jī)Host可以在設(shè)置在使用時在部署靜態(tài)資源或應(yīng)用程序狸棍。

3.4 熱部署

虛擬主機(jī)Host會定期檢查appBase和xmlBase目錄下新Web應(yīng)用程序或靜態(tài)資源身害,如果發(fā)生更新則會觸發(fā)對應(yīng)context組件的重新加載

3.5 別名

虛擬主機(jī)Host可以定義別名。

3.6 管理子容器組件

StandardHost并管理子容器Context組件草戈,以及從父類ContainerBase塌鸯,LifecycleBase 繼承的通用組件。

4 StandardHost職能&屬性

StandardHost實現(xiàn)了Host接口唐片,在了解StandardHost功能之前我們需要了解
Host接口.

Host接口

public interface Host extends Container {

    public static final String ADD_ALIAS_EVENT = "addAlias";

    public static final String REMOVE_ALIAS_EVENT = "removeAlias";

    /**
     * 返回配置文件相對路徑丙猬,配置文件完整路徑為$catalinaBase/xmlBase
     */
    public String getXmlBase();

    /**
     * 設(shè)置配置文件相對路徑
     */
    public void setXmlBase(String xmlBase);

    /**
     * 返回配置文件對象,對應(yīng)完整路徑為$catalinaBase/xmlBase
     */
    public File getConfigBaseFile();

    /**
     * 返回掃描的應(yīng)用程序相對路徑或絕對路徑费韭,如果是相對路徑則完整路徑為$catalinaHome/appBase
     */
    public String getAppBase();

    /**
     * 設(shè)置掃描的應(yīng)用程序相對路徑或絕對路徑
     */
    public void setAppBase(String appBase);

    /**
     * 返回掃描應(yīng)用程序目錄的文件對象
     */
    public File getAppBaseFile();

    /**
     * 是否開啟熱部署
     */
    public boolean getAutoDeploy();

    /**
     * 設(shè)置是否支持熱部署
     */
    public void setAutoDeploy(boolean autoDeploy);

    /**
     * 獲取子組件Context配置實現(xiàn)類茧球,默認(rèn)org.apache.catalina.startup.ContextConfig
     */
    public String getConfigClass();

    /**
     * 設(shè)置子組件Context配置實現(xiàn)類
     */
    public void setConfigClass(String configClass);

    /**
     * 返回是否在啟動Host組件時是否應(yīng)自動部署Host組件的Web應(yīng)用程序
     */
    public boolean getDeployOnStartup();

    /**
     * 設(shè)置是否在啟動Host組件時是否應(yīng)自動部署Host組件的Web應(yīng)用程序
     */
    public void setDeployOnStartup(boolean deployOnStartup);

    /**
     * 返回正則表達(dá)式,用String表示星持,用來定義自動部署哪些應(yīng)用程序
     */
    public String getDeployIgnore();

    /**
     * 返回正則表達(dá)式抢埋,用來定義自動部署哪些應(yīng)用程序
     */
    public Pattern getDeployIgnorePattern();

    /**
     * 設(shè)置正則表達(dá)式,用String表示督暂,用來定義自動部署哪些應(yīng)用程序
     */
    public void setDeployIgnore(String deployIgnore);

    /**
     * 返回處理子容器啟動關(guān)閉線程池
     */
    public ExecutorService getStartStopExecutor();

    /**
     * 返回是否需要在啟動時創(chuàng)建appbase和xmlbase目錄
     */
    public boolean getCreateDirs();

    /**
     * 設(shè)置是否需要在啟動時創(chuàng)建appbase和xmlbase目錄
     */
    public void setCreateDirs(boolean createDirs);

    /**
     * 返回是否檢查現(xiàn)在可以取消部署的舊版本的應(yīng)用程序
     */
    public boolean getUndeployOldVersions();

    /**
     * 設(shè)置是否檢查現(xiàn)在可以取消部署的舊版本的應(yīng)用程序
     */
    public void setUndeployOldVersions(boolean undeployOldVersions);

    /**
     * 給Host組件添加別名
     */
    public void addAlias(String alias);

    /**
     * 返回Host組件所有別名
     */
    public String[] findAliases();

    /**
     * 給Host組件刪除別名
     */
    public void removeAlias(String alias);
}

StandardHost實現(xiàn)Host接口羹令,Host接口用來對Tomcat中虛擬主機(jī)功能配置提供了訪問方法。

4.1 StandardHost職能

StandardHost只對虛擬機(jī)功能配置做了定義损痰,其具體實現(xiàn)由HostConfig來實現(xiàn)福侈。同時負(fù)責(zé)管理子容器Context組件(下圖藍(lán)色),以及從父類ContainerBase(下圖紅色)卢未,LifecycleBase(下圖黃色) 繼承的通用組件肪凛。

image
4.2 核心屬性
    /**
     * Host組件別名
     */
    private String[] aliases = new String[0];

    /**
     * 處理Host組件別名同步鎖對象
     */
    private final Object aliasesLock = new Object();

    /**
     * appBase表示掃描的應(yīng)用程序相對路徑或絕對路徑堰汉,如果是相對路徑則完整路徑為$catalinaHome/appBase
     */
    private String appBase = "webapps";

    /**
     * appBaseFile表示掃描的應(yīng)用程序目錄的文件對象
     */
    private volatile File appBaseFile = null;

    /**
     * xmlBase表示配置文件相對路徑,配置文件路徑為$catalinaBase/xmlBase
     */
    private String xmlBase = null;

    /**
     * hostConfigBase表示配置文件對象伟墙,對應(yīng)路徑為$catalinaBase/xmlBase
     */
    private volatile File hostConfigBase = null;

    /**
     * 是否支持熱部署
     * 如果為true翘鸭,虛擬主機(jī)Host會定期檢查appBase和xmlBase目錄下新Web應(yīng)用程序或靜態(tài)資源,如果發(fā)生更新則會觸發(fā)對應(yīng)context組件的重新加載
     */
    private boolean autoDeploy = true;

    /**
     * 子組件Context配置實現(xiàn)類
     */
    private String configClass =
        "org.apache.catalina.startup.ContextConfig";

    /**
     * 子組件Context實現(xiàn)類
     */
    private String contextClass =
        "org.apache.catalina.core.StandardContext";

    /**
     * 標(biāo)識在啟動Host組件時是否應(yīng)自動部署Host組件的Web應(yīng)用程序戳葵。標(biāo)志的值默認(rèn)為true就乓。
     */
    private boolean deployOnStartup = true;

    /**
     * 是否要禁止應(yīng)用程序中定義/META-INF/context.xml
     */
    private boolean deployXML = !Globals.IS_SECURITY_ENABLED;

    /**
     * 如果在應(yīng)用程序中定義了/META-INF/context.xml,是否要拷貝到$catalinaBase/xmlBase目錄下
     */
    private boolean copyXML = false;

    /**
     * Host組件子組件Pilpline組件內(nèi)處理異常Valve實現(xiàn)類
     */
    private String errorReportValveClass =
        "org.apache.catalina.valves.ErrorReportValve";

    /**
     * 是否解壓war包種應(yīng)用程序在執(zhí)行拱烁,默認(rèn)為true
     */
    private boolean unpackWARs = true;

    /**
     * 標(biāo)識host組件工作的臨時目錄
     * $catalinaBase/workDir
     */
    private String workDir = null;

    /**
     * 標(biāo)識是否需要在啟動時創(chuàng)建appbase和xmlbase目錄
     */
    private boolean createDirs = true;

5 StandardHost運(yùn)行流程

tomcat中所有組件都需要經(jīng)歷如下流程生蚁。

image
5.1 構(gòu)建StandardHost

Tomcat使用Digester解析server.xml,Digester是一款用于將xml轉(zhuǎn)換為Java對象的事件驅(qū)動型工具戏自,是對SAX的高層次的封裝邦投。相對于SAX,Digester可以針對每一個xml標(biāo)簽設(shè)置對應(yīng)的解析規(guī)則擅笔。詳見 Tomcat相關(guān)技術(shù)-Digester(二)

Tomcat在Catalina組件初始化階段調(diào)用createStartDigester()創(chuàng)建Digester對象志衣,Digester對象內(nèi)包含解析server.xml規(guī)則,接著通過Digester對象解析server.xml實例化StandardHost猛们,并對部分屬性設(shè)置值.

server.xml配置

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>

解析<Host>標(biāo)簽及子標(biāo)簽tomcat使用規(guī)則組HostRuleSet念脯,其中定義了解析規(guī)則。

5.1.1 解析<Host>標(biāo)簽
        //解析<Server><Service><Engine><Host>標(biāo)簽
        /** 解析<Host>標(biāo)簽實例化StandardHost對象弯淘,并push到操作棧中 **/
        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");

        /** 解析<Host>標(biāo)簽將標(biāo)簽中屬性值映射到其實例化對象中**/
        digester.addSetProperties(prefix + "Host");


        /** 解析<Host>標(biāo)簽绿店,使用CopyParentClassLoaderRule規(guī)則,負(fù)責(zé)調(diào)用次棧頂對象getParentClassLoader獲取父類加載耳胎,設(shè)置到棧頂對象parentClassLoader屬性上 **/
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());

        /** 解析<Host>標(biāo)簽惯吕,使用LifecycleListenerRule規(guī)則,負(fù)責(zé)給棧頂對象添加一個生命周期監(jiān)聽器. 默認(rèn)為hostConfigClass怕午,或者在標(biāo)簽指定org.apache.catalina.startup.HostConfig屬性**/
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));

        /** 解析<Host>標(biāo)簽將操作棧棧頂對象作為次棧頂對象StandardService.addChild方法調(diào)用的參數(shù)废登,即將實例化StandardHost對象添加StandardServer.child子容器列表屬性中**/
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");
5.1.2 解析<Listener>標(biāo)簽
        /** 解析<Listener>標(biāo)簽實例化標(biāo)簽中className屬性定義的對象,并push到操作棧中 **/
        digester.addObjectCreate(prefix + "Host/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        /** 解析<Listener>標(biāo)簽將標(biāo)簽中屬性值映射到其實例化對象中**/
        digester.addSetProperties(prefix + "Host/Listener");
        /** 解析</Listener>標(biāo)簽將操作棧棧頂對象作為次棧頂對象StandardHost.addLifecycleListener方法調(diào)用的參數(shù)郁惜,設(shè)置到StandardHost屬性中**/
        digester.addSetNext(prefix + "Host/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
5.1.3 解析<Valve>標(biāo)簽
        /** 解析<Valve>標(biāo)簽實例化標(biāo)簽中className屬性定義的對象堡距,并push到操作棧中 **/
        digester.addObjectCreate(prefix + "Host/Valve",
                                 null,
                                 "className");
        /** 解析<Valve>標(biāo)簽將標(biāo)簽中屬性值映射到其實例化對象中**/
        digester.addSetProperties(prefix + "Host/Valve");
        /** 解析</Valve>標(biāo)簽將操作棧棧頂對象作為次棧頂對象StandardHost.addValve方法調(diào)用的參數(shù),設(shè)置到StandardHost屬性中**/
        digester.addSetNext(prefix + "Host/Valve",
                            "addValve",
                            "org.apache.catalina.Valve");
5.1.4 解析<Alias>標(biāo)簽兆蕉,
/** 解析<Alias>標(biāo)簽羽戒,將標(biāo)簽中數(shù)據(jù)<Alias>test<Alias>做為參數(shù)調(diào)用棧頂對象StandardHost.addAlias方法調(diào)用的參數(shù),設(shè)置到StandardHost屬性中 **/
        digester.addCallMethod(prefix + "Host/Alias",
                               "addAlias", 0);
5.1.5 解析<Realm>標(biāo)簽
    //解析<Server><Service><Engine><Host><Realm>標(biāo)簽
    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
        
        
    /**
     * 解析Realm標(biāo)簽添加到棧頂對象屬性中
     */
    @SuppressWarnings("deprecation")
    public class RealmRuleSet extends RuleSetBase {  
    
        ...省略代碼

        @Override
        public void addRuleInstances(Digester digester) {
            StringBuilder pattern = new StringBuilder(prefix);
            for (int i = 0; i < MAX_NESTED_REALM_LEVELS; i++) {
                if (i > 0) {
                    pattern.append('/');
                }
                pattern.append("Realm");
                addRuleInstances(digester, pattern.toString(), i == 0 ? "setRealm" : "addRealm");
            }
        }
    
        private void addRuleInstances(Digester digester, String pattern, String methodName) {
            digester.addObjectCreate(pattern, null /* MUST be specified in the element */,
                    "className");
            digester.addSetProperties(pattern);
            digester.addSetNext(pattern, methodName, "org.apache.catalina.Realm");
            digester.addRuleSet(new CredentialHandlerRuleSet(pattern + "/"));
        }
5.1.6 重要的規(guī)則

CopyParentClassLoaderRule規(guī)則

CopyParentClassLoaderRule規(guī)則虎韵,負(fù)責(zé)調(diào)用次棧頂對象getParentClassLoader獲取父類加載易稠,設(shè)置到棧頂對象parentClassLoader屬性上

public class CopyParentClassLoaderRule extends Rule {


    public CopyParentClassLoaderRule() {
    }
    
    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        if (digester.getLogger().isDebugEnabled())
            digester.getLogger().debug("Copying parent class loader");
        Container child = (Container) digester.peek(0);
        Object parent = digester.peek(1);
        Method method =
            parent.getClass().getMethod("getParentClassLoader", new Class[0]);
        ClassLoader classLoader =
            (ClassLoader) method.invoke(parent, new Object[0]);
        child.setParentClassLoader(classLoader);

    }
}

LifecycleListenerRule規(guī)則

LifecycleListenerRule 規(guī)則負(fù)責(zé)給棧頂對象添加一個生命周期監(jiān)聽器.

/**
 *  解析標(biāo)簽給棧頂對象添加一個生命周期監(jiān)聽器
 */
public class LifecycleListenerRule extends Rule {
    
    public LifecycleListenerRule(String listenerClass, String attributeName) {
        this.listenerClass = listenerClass;
        this.attributeName = attributeName;
    }
    /**
     * 標(biāo)準(zhǔn)中指定屬性,用來設(shè)置監(jiān)聽器處理類
     */
    private final String attributeName;

    /**
     * 默認(rèn)監(jiān)聽器處理類
     */
    private final String listenerClass;
    
    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        /** 獲取棧頂原始對象 **/
        Container c = (Container) digester.peek();

        /** 獲取次棧頂元素對象 **/
        Container p = null;
        Object obj = digester.peek(1);

        /** 如果棧頂元素對象是容器設(shè)置給p **/
        if (obj instanceof Container) {
            p = (Container) obj;
        }

        String className = null;

        /** 獲取標(biāo)簽attributeName值賦值給className **/
        if (attributeName != null) {
            String value = attributes.getValue(attributeName);
            if (value != null)
                className = value;
        }

        /** 獲取次棧頂對象attributeName屬性值賦值給className **/
        if (p != null && className == null) {
            String configClass =
                (String) IntrospectionUtils.getProperty(p, attributeName);
            if (configClass != null && configClass.length() > 0) {
                className = configClass;
            }
        }

        /** 如果className == null使用listenerClass作為className默認(rèn)值**/
        if (className == null) {
            className = listenerClass;
        }


        /** 實例化className添加棧頂對象生命周期監(jiān)聽器列表中*/
        Class<?> clazz = Class.forName(className);
        LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
        c.addLifecycleListener(listener);
    }
}

組件生命周期

接下來初始化開始則進(jìn)入tomcat組件的生命周期包蓝,對于tomcat中所有組件都必須實現(xiàn)Lifecycle驶社,Tomcat 定義一個基類LifecycleBase 來實現(xiàn) Lifecycle 接口企量,把一些公共的邏輯放到基類中實現(xiàn),比如生命狀態(tài)的轉(zhuǎn)變與維護(hù)亡电、生命事件的觸發(fā)以及監(jiān)聽器的添加和刪除等届巩,而子類就負(fù)責(zé)實現(xiàn)自己的初始化、啟動和停止等模板方法份乒。為了避免跟基類中的方法同名恕汇,我們把具體子類的實現(xiàn)方法改個名字,在后面加上 Internal或辖,叫 initInternal瘾英、startInternal 等。

StandardHost父類對容器的初始化孝凌、啟動和停止等模板方法進(jìn)行的了默認(rèn)實現(xiàn)方咆。子類容器只需要重寫父類實現(xiàn)即可實現(xiàn)擴(kuò)展月腋。

5.2 啟動StandardHost
    /**
     * 啟動組件模板實現(xiàn)
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        /** 將errorReportValveClass 類對象添加到Host組件Pipeline組件內(nèi) **/
        String errorValve = getErrorReportValveClass();
        if ((errorValve != null) && (!errorValve.equals(""))) {
            try {
                boolean found = false;
                Valve[] valves = getPipeline().getValves();
                for (Valve valve : valves) {
                    if (errorValve.equals(valve.getClass().getName())) {
                        found = true;
                        break;
                    }
                }
                if(!found) {
                    Valve valve =
                        (Valve) Class.forName(errorValve).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        super.startInternal();
    }

StandardEngine其他生命周期實現(xiàn)均從父類ContainerBase繼承蟀架。

6 核心方法

6.1 重寫addChild

為添加的子容器設(shè)置生命周期監(jiān)聽器MemoryLeakTrackingListener

    /**
     * 添加一個子容器
     */
    @Override
    public void addChild(Container child) {
        /** 給子容器組件添加MemoryLeakTrackingListener監(jiān)聽器 **/
        child.addLifecycleListener(new MemoryLeakTrackingListener());

        if (!(child instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("standardHost.notContext"));
        super.addChild(child);
    }


    /**
     * 處理AFTER_START_EVENT生命周期事件,設(shè)置childClassLoaders屬性
     */
    private class MemoryLeakTrackingListener implements LifecycleListener {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
                if (event.getSource() instanceof Context) {
                    Context context = ((Context) event.getSource());
                    childClassLoaders.put(context.getLoader().getClassLoader(),
                            context.getServletContext().getContextPath());
                }
            }
        }
    }
6.2 管理別名
/**
     * 給host組件添加別名
     */
    @Override
    public void addAlias(String alias) {
        alias = alias.toLowerCase(Locale.ENGLISH);
        synchronized (aliasesLock) {
            // Skip duplicate aliases
            for (int i = 0; i < aliases.length; i++) {
                if (aliases[i].equals(alias))
                    return;
            }
            // Add this alias to the list
            String newAliases[] = new String[aliases.length + 1];
            for (int i = 0; i < aliases.length; i++)
                newAliases[i] = aliases[i];
            newAliases[aliases.length] = alias;
            aliases = newAliases;
        }
        /** 觸發(fā)屬性變更 **/
        fireContainerEvent(ADD_ALIAS_EVENT, alias);
    }

    /**
     * 返回host組件所有別名
     */
    @Override
    public String[] findAliases() {
        synchronized (aliasesLock) {
            return (this.aliases);
        }
    }


    /**
     * 刪除host組件別名
     */
    @Override
    public void removeAlias(String alias) {

        alias = alias.toLowerCase(Locale.ENGLISH);

        synchronized (aliasesLock) {

            int n = -1;
            for (int i = 0; i < aliases.length; i++) {
                if (aliases[i].equals(alias)) {
                    n = i;
                    break;
                }
            }
            if (n < 0)
                return;

            int j = 0;
            String results[] = new String[aliases.length - 1];
            for (int i = 0; i < aliases.length; i++) {
                if (i != n)
                    results[j++] = aliases[i];
            }
            aliases = results;
        }

        // Inform interested listeners
        fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
    }

7 處理流程

每一個容器組件都有一個 Pipeline 對象榆骚,Pipeline 中維護(hù)了 Valve 鏈表片拍,默認(rèn)時每一個Pipeline存放了一個默認(rèn)的BasicValue,
這里每一個Value表示一個處理點,當(dāng)調(diào)用addValve 方法時會將添加Vaule添加到鏈表頭部妓肢,Pipeline 中沒有 invoke
方法捌省,請求處理時Pipeline只需要獲取鏈表中第一個Valve調(diào)用incoke去執(zhí)行,執(zhí)行完畢后當(dāng)前Value會調(diào)用
getNext.invoke() 來觸發(fā)下一個 Valve 調(diào)用

每一個容器在執(zhí)行到最后一個默認(rèn)BasicValue時碉钠,會負(fù)責(zé)調(diào)用下層容器的 Pipeline 里的第一個 Valve

image

對于StandardHost容器來說默認(rèn)情況存在三個Value(閥門)纲缓,分別是AccessLogValve(構(gòu)建時讀取server.xml時),StandardHostValve(構(gòu)建實例化時)喊废,ErrorReportValve(啟動時)祝高。

7.1 ErrorReportValve

public class ErrorReportValve extends ValveBase {

    private boolean showReport = true;

    private boolean showServerInfo = true;

    Constructor
    public ErrorReportValve() {
        super(true);
    }

@Override
    public void invoke(Request request, Response response) throws IOException, ServletException {

        // 調(diào)用下一個Value處理
        getNext().invoke(request, response);

        /** 是否已提交此響應(yīng)的輸出 **/
        if (response.isCommitted()) {
            /** 未處理以提交設(shè)置響應(yīng)錯誤**/
            if (response.setErrorReported()) {
                /** 清理response緩沖區(qū) **/
                try {
                    response.flushBuffer();
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                }
                /** response 設(shè)置關(guān)閉連接發(fā)出錯誤**/
                response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
                        request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
            }
            return;
        }
        /** 獲取異常 **/
        Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        /** 如果是異步處理直接返回 **/
        if (request.isAsync() && !request.isAsyncCompleting()) {
            return;
        }

        /** 發(fā)生一次設(shè)置http響應(yīng)編碼 500**/
        if (throwable != null && !response.isError()) {
            response.reset();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

        /** 設(shè)置掛起的標(biāo)志。**/
        response.setSuspended(false);

        /** 打印錯誤報告 **/
        try {
            report(request, response, throwable);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
    }
    ...省略代碼

7.2 AccessLogValve

記錄訪問日志污筷,這里是一個通用組件工闺,后續(xù)會由專題講解

7.3 StandardHostValve

最後編輯於
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓣蛀,隨后出現(xiàn)的幾起案子陆蟆,更是在濱河造成了極大的恐慌,老刑警劉巖惋增,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叠殷,死亡現(xiàn)場離奇詭異,居然都是意外死亡诈皿,警方通過查閱死者的電腦和手機(jī)林束,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門钩杰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诊县,你說我怎么就攤上這事讲弄。” “怎么了依痊?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵避除,是天一觀的道長。 經(jīng)常有香客問我胸嘁,道長瓶摆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任性宏,我火速辦了婚禮群井,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毫胜。我一直安慰自己书斜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布酵使。 她就那樣靜靜地躺著荐吉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪口渔。 梳的紋絲不亂的頭發(fā)上样屠,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機(jī)與錄音缺脉,去河邊找鬼痪欲。 笑死,一個胖子當(dāng)著我的面吹牛攻礼,可吹牛的內(nèi)容都是我干的业踢。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼秘蛔,長吁一口氣:“原來是場噩夢啊……” “哼陨亡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起深员,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤负蠕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后倦畅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遮糖,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年叠赐,在試婚紗的時候發(fā)現(xiàn)自己被綠了欲账。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屡江。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赛不,靈堂內(nèi)的尸體忽然破棺而出惩嘉,到底是詐尸還是另有隱情,我是刑警寧澤踢故,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布文黎,位于F島的核電站,受9級特大地震影響殿较,放射性物質(zhì)發(fā)生泄漏耸峭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一淋纲、第九天 我趴在偏房一處隱蔽的房頂上張望劳闹。 院中可真熱鬧,春花似錦洽瞬、人聲如沸本涕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偏友。三九已至蔬胯,卻和暖如春对供,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氛濒。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工产场, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舞竿。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓京景,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骗奖。 傳聞我的和親對象是個殘疾皇子确徙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

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