本文接下来分析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 ( #改为@ )

