本文接下來分析Context容器,Context容器實例表示一個具體的Web應用程序,其中包括一個或多個Wrapper實例;不過Context容器還需要其他的組件支持,典型的如載入器和Session管理器等。
在創建StandardContext實例后,必須調用其start()方法來為引入的每個HTTP請求服務;其中包括讀取和解析默認的web.xml文件(該文件位于%CATALINA_HOME%/conf目錄),該文件的內容會應用到所有部署到tomcat中的應用程序中;此外,還會配置驗證器閥和許可閥。
StandardContext類使用一個事件監聽器來作為其配置器(前面我們已經學過在SimpleContextConfig事件監聽器中配置驗證器閥)
public
synchronized
void
start()
throws
LifecycleException {
if
(started)
throw
new
LifecycleException
(sm.getString(
"containerBase.alreadyStarted"
, logName()));
if
(debug >= 1
)
log(
"Starting"
);
//
Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,
null
);
if
(debug >= 1
)
log(
"Processing start(), current available=" +
getAvailable());
setAvailable(
false
);
setConfigured(
false
);
boolean
ok =
true
;
//
Add missing components as necessary
if
(getResources() ==
null
) {
//
(1) Required by Loader
if
(debug >= 1
)
log(
"Configuring default Resources"
);
try
{
if
((docBase !=
null
) && (docBase.endsWith(".war"
)))
setResources(
new
WARDirContext());
else
setResources(
new
FileDirContext());
}
catch
(IllegalArgumentException e) {
log(
"Error initializing resources: " +
e.getMessage());
ok
=
false
;
}
}
if
(ok && (resources
instanceof
ProxyDirContext)) {
DirContext dirContext
=
((ProxyDirContext) resources).getDirContext();
if
((dirContext !=
null
)
&& (dirContext
instanceof
BaseDirContext)) {
((BaseDirContext) dirContext).setDocBase(getBasePath());
((BaseDirContext) dirContext).allocate();
}
}
if
(getLoader() ==
null
) {
//
(2) Required by Manager
if
(getPrivileged()) {
if
(debug >= 1
)
log(
"Configuring privileged default Loader"
);
setLoader(
new
WebappLoader(
this
.getClass().getClassLoader()));
}
else
{
if
(debug >= 1
)
log(
"Configuring non-privileged default Loader"
);
setLoader(
new
WebappLoader(getParentClassLoader()));
}
}
if
(getManager() ==
null
) {
//
(3) After prerequisites
if
(debug >= 1
)
log(
"Configuring default Manager"
);
setManager(
new
StandardManager());
}
//
Initialize character set mapper
getCharsetMapper();
//
Post work directory
postWorkDirectory();
//
Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming"
);
if
((useNamingProperty !=
null
)
&& (useNamingProperty.equals("false"
))) {
useNaming
=
false
;
}
if
(ok &&
isUseNaming()) {
if
(namingContextListener ==
null
) {
namingContextListener
=
new
NamingContextListener();
namingContextListener.setDebug(getDebug());
namingContextListener.setName(getNamingContextName());
addLifecycleListener(namingContextListener);
}
}
//
Binding thread
ClassLoader oldCCL =
bindThread();
//
Standard container startup
if
(debug >= 1
)
log(
"Processing standard container startup"
);
if
(ok) {
try
{
addDefaultMapper(
this
.mapperClass);
started
=
true
;
//
Start our subordinate components, if any
if
((loader !=
null
) && (loader
instanceof
Lifecycle))
((Lifecycle) loader).start();
if
((logger !=
null
) && (logger
instanceof
Lifecycle))
((Lifecycle) logger).start();
//
Unbinding thread
unbindThread(oldCCL);
//
Binding thread
oldCCL =
bindThread();
if
((cluster !=
null
) && (cluster
instanceof
Lifecycle))
((Lifecycle) cluster).start();
if
((realm !=
null
) && (realm
instanceof
Lifecycle))
((Lifecycle) realm).start();
if
((resources !=
null
) && (resources
instanceof
Lifecycle))
((Lifecycle) resources).start();
//
Start our Mappers, if any
Mapper mappers[] =
findMappers();
for
(
int
i = 0; i < mappers.length; i++
) {
if
(mappers[i]
instanceof
Lifecycle)
((Lifecycle) mappers[i]).start();
}
//
Start our child containers, if any
Container children[] =
findChildren();
for
(
int
i = 0; i < children.length; i++
) {
if
(children[i]
instanceof
Lifecycle)
((Lifecycle) children[i]).start();
}
//
Start the Valves in our pipeline (including the basic),
//
if any
if
(pipeline
instanceof
Lifecycle)
((Lifecycle) pipeline).start();
//
Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(START_EVENT,
null
);
if
((manager !=
null
) && (manager
instanceof
Lifecycle))
((Lifecycle) manager).start();
}
finally
{
//
Unbinding thread
unbindThread(oldCCL);
}
}
if
(!
getConfigured())
ok
=
false
;
//
We put the resources into the servlet context
if
(ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
//
Binding thread
oldCCL =
bindThread();
//
Create context attributes that will be required
if
(ok) {
if
(debug >= 1
)
log(
"Posting standard context attributes"
);
postWelcomeFiles();
}
//
Configure and call application event listeners and filters
if
(ok) {
if
(!
listenerStart())
ok
=
false
;
}
if
(ok) {
if
(!
filterStart())
ok
=
false
;
}
//
Load and initialize all "load on startup" servlets
if
(ok)
loadOnStartup(findChildren());
//
Unbinding thread
unbindThread(oldCCL);
//
Set available status depending upon startup success
if
(ok) {
if
(debug >= 1
)
log(
"Starting completed"
);
setAvailable(
true
);
}
else
{
log(sm.getString(
"standardContext.startFailed"
));
try
{
stop();
}
catch
(Throwable t) {
log(sm.getString(
"standardContext.startCleanup"
), t);
}
setAvailable(
false
);
}
//
Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT,
null
);
}
在它的start()方法里面,包括初始化相關容器組件、觸發相關事件等(ContextConfig監聽器會執行一些配置操作)
StandardContext類的invoke()方法由與其相關聯的連接器調用,或者當StandardContext實例是Host容器的子容器時,由Host實例的invoke()方法調用
public
void
invoke(Request request, Response response)
throws
IOException, ServletException {
//
Wait if we are reloading
while
(getPaused()) {
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
;
}
}
//
Normal request processing
if
(swallowOutput) {
try
{
SystemLogHandler.startCapture();
super
.invoke(request, response);
}
finally
{
String log
=
SystemLogHandler.stopCapture();
if
(log !=
null
&& log.length() > 0
) {
log(log);
}
}
}
else
{
super
.invoke(request, response);
}
}
對于每個引入的HTTP請求,都會調用StandardContext實例的管道對象的基礎閥的invoke()方法來處理,這里是org.apache.catalina.core.StandardContextValve類的實例;StandardContextValve類的invoke()方法要做的第一件事是獲取一個要處理當前HTTP請求的Wrapper實例;StandardContextValve實例使用StandardContext實例的映射器找到一個合適的Wrapper實例,找到Wrapper實例后,它就會調用Wrapper實例的invoke()方法
下面是standardContextMapper類的map()方法
public
Container map(Request request,
boolean
update) {
int
debug =
context.getDebug();
//
Has this request already been mapped?
if
(update && (request.getWrapper() !=
null
))
return
(request.getWrapper());
//
Identify the context-relative URI to be mapped
String contextPath =
((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI
=
((HttpRequest) request).getDecodedRequestURI();
String relativeURI
=
requestURI.substring(contextPath.length());
if
(debug >= 1
)
context.log(
"Mapping contextPath='" + contextPath +
"' with requestURI='" + requestURI +
"' and relativeURI='" + relativeURI + "'"
);
//
Apply the standard request URI mapping rules from the specification
Wrapper wrapper =
null
;
String servletPath
=
relativeURI;
String pathInfo
=
null
;
String name
=
null
;
//
Rule 1 -- Exact Match
if
(wrapper ==
null
) {
if
(debug >= 2
)
context.log(
" Trying exact match"
);
if
(!(relativeURI.equals("/"
)))
name
=
context.findServletMapping(relativeURI);
if
(name !=
null
)
wrapper
=
(Wrapper) context.findChild(name);
if
(wrapper !=
null
) {
servletPath
=
relativeURI;
pathInfo
=
null
;
}
}
//
Rule 2 -- Prefix Match
if
(wrapper ==
null
) {
if
(debug >= 2
)
context.log(
" Trying prefix match"
);
servletPath
=
relativeURI;
while
(
true
) {
name
= context.findServletMapping(servletPath + "/*"
);
if
(name !=
null
)
wrapper
=
(Wrapper) context.findChild(name);
if
(wrapper !=
null
) {
pathInfo
=
relativeURI.substring(servletPath.length());
if
(pathInfo.length() == 0
)
pathInfo
=
null
;
break
;
}
int
slash = servletPath.lastIndexOf('/'
);
if
(slash < 0
)
break
;
servletPath
= servletPath.substring(0
, slash);
}
}
//
Rule 3 -- Extension Match
if
(wrapper ==
null
) {
if
(debug >= 2
)
context.log(
" Trying extension match"
);
int
slash = relativeURI.lastIndexOf('/'
);
if
(slash >= 0
) {
String last
=
relativeURI.substring(slash);
int
period = last.lastIndexOf('.'
);
if
(period >= 0
) {
String pattern
= "*" +
last.substring(period);
name
=
context.findServletMapping(pattern);
if
(name !=
null
)
wrapper
=
(Wrapper) context.findChild(name);
if
(wrapper !=
null
) {
servletPath
=
relativeURI;
pathInfo
=
null
;
}
}
}
}
//
Rule 4 -- Default Match
if
(wrapper ==
null
) {
if
(debug >= 2
)
context.log(
" Trying default match"
);
name
= context.findServletMapping("/"
);
if
(name !=
null
)
wrapper
=
(Wrapper) context.findChild(name);
if
(wrapper !=
null
) {
servletPath
=
relativeURI;
pathInfo
=
null
;
}
}
//
Update the Request (if requested) and return this Wrapper
if
((debug >= 1) && (wrapper !=
null
))
context.log(
" Mapped to servlet '" + wrapper.getName() +
"' with servlet path '" + servletPath +
"' and path info '" + pathInfo +
"' and update=" +
update);
if
(update) {
request.setWrapper(wrapper);
((HttpRequest) request).setServletPath(servletPath);
((HttpRequest) request).setPathInfo(pathInfo);
}
return
(wrapper);
}
standardContextMapper實例必須與一個Context級的容器相關聯(在它的map()方法中調用了Context容器實例的相關方法)
standardContext類定義了reloadable屬性來指明該應用程序是否啟用了重載功能,當啟用重載功能后,當web.xml文件發生變化或WEB-INF/classes目錄下的文件被重新編譯后,應用程序會重載。
standardContext類是通過其載入器實現應用程序重載的,在tomcat4中,standardContext對象中的WebappLoader類實現了Loader接口,并使用另一個線程檢查WEB-INF目錄中的所有類和JAR文件的時間戳。只需要調用其setContainer()方法將WebappLoader對象與standardContext對象相關聯就可以啟動該檢查線程
下面是tomcat4中WebappLoader類的setContainer()方法的實現代碼
public
void
setContainer(Container container) {
//
Deregister from the old Container (if any)
if
((
this
.container !=
null
) && (
this
.container
instanceof
Context))
((Context)
this
.container).removePropertyChangeListener(
this
);
//
Process this property change
Container oldContainer =
this
.container;
this
.container =
container;
support.firePropertyChange(
"container", oldContainer,
this
.container);
//
Register with the new Container (if any)
if
((
this
.container !=
null
) && (
this
.container
instanceof
Context)) {
setReloadable( ((Context)
this
.container).getReloadable() );
((Context)
this
.container).addPropertyChangeListener(
this
);
}
}
WebappLoader實例的reloadable屬性值與standardContext實例的reloadable屬性值是一致的
下面是WebappLoader類的setReloadable()方法的實現代碼:
public
void
setReloadable(
boolean
reloadable) {
//
Process this property change
boolean
oldReloadable =
this
.reloadable;
this
.reloadable =
reloadable;
support.firePropertyChange(
"reloadable"
,
new
Boolean(oldReloadable),
new
Boolean(
this
.reloadable));
//
Start or stop our background thread if required
if
(!
started)
return
;
if
(!oldReloadable &&
this
.reloadable)
threadStart();
else
if
(oldReloadable && !
this
.reloadable)
threadStop();
}
里面的threadStart()方法會啟動一個專用的線程來不斷地檢查WEB-INF目錄下的類和JAR文件的時間戳,而threadStop()方法則會終止該線程。
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

