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組件尽棕;
- ...