tomcat4.0版本。
Context容器对应一个web项目,由host容器根据url中的context匹配到,其作用是根据url中的context后的路径匹配合适的servlet来处理请求。
/**
* Create a new StandardContext component with the default basic Valve.
*/
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
namingResources.setContainer(this);
}
构造函数设置pipeline和基本的valve。
接着看下其start方法,由父容器调用启动一个context容器:
/**
* Start this Context component.
*
* @exception LifecycleException if a startup error occurs
*/
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 (webappResources == 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) {
if (!resourcesStart())
ok = false;
}
// Install DefaultContext configuration
if (!getOverride()) {
Container host = getParent();
if (host instanceof StandardHost) {
((StandardHost)host).installDefaultContext(this);
Container engine = host.getParent();
if( engine instanceof StandardEngine ) {
((StandardEngine)engine).installDefaultContext(this);
}
}
}
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");
try {
if (ok) {
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();
try {
// 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;
}
} finally {
// 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);
throw new LifecycleException(sm.getString("standardContext.startFailed"));
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
// Load and initialize all "load on startup" servlets
oldCCL = bindThread();
try {
loadOnStartup(findChildren());
} finally {
unbindThread(oldCCL);
}
}
/**
* Stop this Context component.
*
* @exception LifecycleException if a shutdown error occurs
*/
public synchronized void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException
(sm.getString("containerBase.notStarted", logName()));
if (debug >= 1)
log("Stopping");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
// Mark this application as unavailable while we shut down
setAvailable(false);
// Binding thread
ClassLoader oldCCL = bindThread();
try {
// Stop our filters
filterStop();
// Finalize our character set mapper
setCharsetMapper(null);
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) manager).stop();
}
// Normal container shutdown processing
if (debug >= 1)
log("Processing standard container shutdown");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
// Stop the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).stop();
}
// Stop our child containers, if any
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Lifecycle)
((Lifecycle) children[i]).stop();
}
// Stop our application listeners
listenerStop();
// Clear all application-originated servlet context attributes
if (context != null)
context.clearAttributes();
// Stop our Mappers, if any
Mapper mappers[] = findMappers();
for (int i = 0; i < mappers.length; i++) {
if (mappers[(mappers.length-1)-i] instanceof Lifecycle)
((Lifecycle) mappers[(mappers.length-1)-i]).stop();
}
// Stop resources
resourcesStop();
if ((realm != null) && (realm instanceof Lifecycle)) {
((Lifecycle) realm).stop();
}
if ((cluster != null) && (cluster instanceof Lifecycle)) {
((Lifecycle) cluster).stop();
}
if ((logger != null) && (logger instanceof Lifecycle)) {
((Lifecycle) logger).stop();
}
if ((loader != null) && (loader instanceof Lifecycle)) {
((Lifecycle) loader).stop();
}
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Reset application context
context = null;
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
if (debug >= 1)
log("Stopping complete");
}
这个方法很长,主要做了一下事情:
触发生命周期的相关事件;设置一个mapper的路径匹配器;设置加载器;启动子容器,启动流水线等。
先看下路径匹配器的实现:
/**
* The Java class name of the default Mapper class for this Container.
*/
private String mapperClass =
"org.apache.catalina.core.StandardContextMapper";
对应的class是standardContextMapper。
其中最重要的方法是map方法,用于根据url来匹配一个servlet处理:
/**
* Return the child Container that should be used to process this Request,
* based upon its characteristics. If no such child Container can be
* identified, return <code>null</code> instead.
*
* @param request Request being processed
* @param update Update the Request to reflect the mapping selection?
*
* @exception IllegalArgumentException if the relative portion of the
* path cannot be URL decoded
*/
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);
}
可以看到,这便是tomcat中servlet匹配规则的实现,之前在另一篇博客中介绍过这个规则。
首先取出relativeUrl,然后根据这个url来匹配。
1.精确匹配,是调用的context的匹配方法:
/**
* The servlet mappings for this web application, keyed by
* matching pattern.
*/
private HashMap servletMappings = new HashMap();
/**
* Return the servlet name mapped by the specified pattern (if any);
* otherwise return <code>null</code>.
*
* @param pattern Pattern for which a mapping is requested
*/
public String findServletMapping(String pattern) {
synchronized (servletMappings) {
return ((String) servletMappings.get(pattern));
}
}
context会把web.xml里配置的servlet全部根据相对路径存储到一个map中,然后精确匹配就是直接用请求的相对url来从map中取出servlet。
2.接着是路径匹配,也就是通过匹配/path/*的servlet。
我们知道路径匹配会优先匹配最长路径。所以这里的实现是一个while循环,首先用全部的relativeUrl 加上一个 /*来匹配servlet,如果有,那么这就是最长的。否则,那么就把relativeUrl从后向前依次删除一个/ /之间的内容,也就是匹配的路径逐渐缩短,如果还没有一个配置了/path/*的路径可以匹配到,就进入到后缀匹配。
3.后缀匹配,也就是*.jsp类型的。
就是把relativeUrl的“.”后面的类型拿出来,加上一个“*”去匹配。
4.默认匹配,如果没仍然未匹配到,那就使用defaultServlet来处理。
可以看到这个实现和之前博客介绍的规则是完全一致的。
接着,请求到来时,父容器会调用context的invoke方法,然后会调用pipeline的invoke,最后就到了standardContextValve的invoke方法:
/**
* Select the appropriate child Wrapper to process this request,
* based on the specified request URI. If no matching Wrapper can
* be found, return an appropriate HTTP error.
*
* @param request Request to be processed
* @param response Response to be produced
* @param valveContext Valve context used to forward to the next Valve
*
* @exception IOException if an input/output error occurred
* @exception ServletException if a servlet error occurred
*/
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return; // NOTE - Not much else we can do generically
}
// Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI =
requestURI.substring(contextPath.length()).toUpperCase();
if (relativeURI.equals("/META-INF") ||
relativeURI.equals("/WEB-INF") ||
relativeURI.startsWith("/META-INF/") ||
relativeURI.startsWith("/WEB-INF/")) {
notFound((HttpServletResponse) response.getResponse());
return;
}
Context context = (Context) getContainer();
// Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI,
(HttpServletResponse) response.getResponse());
return;
}
if (wrapper == null) {
notFound((HttpServletResponse) response.getResponse());
return;
}
// Ask this Wrapper to process this Request
response.setContext(context);
wrapper.invoke(request, response);
}
可以看到这里就是取出了relativeUrl来调用之前mapper的map方法 匹配一个servlet来处理请求的。
之后会介绍最后一个容器组件wrapper(servlet)。