1 Container容器
Container容器用來表示tomcat中servlet容器,負(fù)責(zé)servelt的加載和管理浆劲,處理請求ServletRequest,并返回標(biāo)準(zhǔn)的 ServletResponse 對象給連接器。
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。
2 StandardHost類結(jié)構(gòu)
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表示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(下圖黃色) 繼承的通用組件肪凛。
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)歷如下流程生蚁。
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 "%r" %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
對于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ù)會由專題講解