前一篇文章讲了org.apache.catalina.startup.HostConfig的lifecycleEvent方法中所做的事情。最后看到在Tomcat启动时或启动后(后台线程定时扫描)会调用HostConfig类的deployApps方法:
- /**
- * Deploy applications for any directories or WAR files that are found
- * in our "application root" directory.
- */
- protected void deployApps() {
- File appBase = appBase();
- File configBase = configBase();
- String[] filteredAppPaths = filterAppPaths(appBase.list());
- // Deploy XML descriptors from configBase
- deployDescriptors(configBase, configBase.list());
- // Deploy WARs
- deployWARs(appBase, filteredAppPaths);
- // Deploy expanded folders
- deployDirectories(appBase, filteredAppPaths);
- }
可以看到这里部署应用有三种方式:XML文件描述符、WAR包、文件目录。三种方式部署的总体流程很相似,都是一个web应用分配一个线程来处理,这里统一放到与Host内部的线程池对象中(startStopExecutor),所以有时会看到在默认配置下Tomcat启动后可能有一个叫“-startStop-”的线程还会运行一段时间才结束。但浏览这三种部署方式的实现代码,里面都是构建一个Context对象,并将构建好的Context对象与Host组件关联起来(即调用host.addChild(context)这句,具体代码在HostConfig类的deployDescriptor(ContextName cn, File contextXml)、deployDirectory(ContextName cn, File dir)、deployWAR(ContextName cn, File war)三个方法中,这里不再贴出代码来详细分析)。
前一篇文章只分析到这步,可以看出与一个web应用相对应的一个Context对象已经构建出来了,但如果容器只执行到这里根本无法响应一个浏览器的一次请求。就web服务器的实现来看一次请求过来除了需要根据内部Context构建找到这次请求访问的web应用具体所对应的Context对象,还需要包含web应用中具体的哪个Servlet来处理这次请求,中间是否还需要执行相应的过滤器(filter)、监听器(listener)等,做过java的web开发的都知道,这些信息是配置在一个web应用的WEB-INF\web.xml文件的(servlet3中已经支持将这些配置信息放到Java文件的注解中,但万变不离其宗,总归要在web应用的某个地方说明,并在容器启动时加载,这样才能真正提供web服务,响应请求)。
看到这里可以猜到Tomcat容器加载web应用时必定会有对于每个应用的web.xml文件的解析过程,本文就来看看这个解析过程。
在本文开头提到的三种部署应用的实现代码中有一些共通的代码,这里摘出来说明一下:
- Class<?> clazz = Class.forName(host.getConfigClass());
- LifecycleListener listener =
- (LifecycleListener) clazz.newInstance();
- context.addLifecycleListener(listener);
- host.addChild(context);
第一段是在所有Context对象构建时会添加一个监听器,这里监听器的类名是StandardHost类的实例变量configClass,其默认值就是org.apache.catalina.startup.ContextConfig。第二段是将当前构建的Context对象添加到父容器Host对象中。
先看下StandardHost的addChild方法的实现:
- public void addChild(Container child) {
- child.addLifecycleListener(new MemoryLeakTrackingListener());
- if (!(child instanceof Context))
- throw new IllegalArgumentException
- (sm.getString("standardHost.notContext"));
- super.addChild(child);
- }
可以看到这段代码最后调用了父类的addChild方法:
- public void addChild(Container child) {
- if (Globals.IS_SECURITY_ENABLED) {
- PrivilegedAction<Void> dp =
- new PrivilegedAddChild(child);
- AccessController.doPrivileged(dp);
- } else {
- addChildInternal(child);
- }
- }
这里看下addChildInternal方法的实现:
- private void addChildInternal(Container child) {
- if( log.isDebugEnabled() )
- log.debug("Add child " + child + " " + this);
- synchronized(children) {
- if (children.get(child.getName()) != null)
- throw new IllegalArgumentException("addChild: Child name '" +
- child.getName() +
- "' is not unique");
- child.setParent(this); // May throw IAE
- children.put(child.getName(), child);
- }
- // Start child
- // Don't do this inside sync block - start can be a slow process and
- // locking the children object can cause problems elsewhere
- if ((getState().isAvailable() ||
- LifecycleState.STARTING_PREP.equals(getState())) &&
- startChildren) {
- try {
- child.start();
- } catch (LifecycleException e) {
- log.error("ContainerBase.addChild: start: ", e);
- throw new IllegalStateException
- ("ContainerBase.addChild: start: " + e);
- }
- }
- fireContainerEvent(ADD_CHILD_EVENT, child);
- }
可以看到会调用子容器的start方法,就是指调用StandardContext的start方法。
即给host对象添加子容器时将会调用子容器的start方法,按照前面文章的分析,调用StandardContext的start方法最终会调用org.apache.catalina.core.StandardContext类的startInternal方法(该方法代码较长,建议自己阅读,不再贴出),这里将会发布一系列事件,按调用前后顺序这些事件包括:BEFORE_INIT_EVENT、AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_START_EVENT、START_EVENT、AFTER_START_EVENT。
前面提到在构建Context对象时都会注册一个监听器org.apache.catalina.startup.ContextConfig,看下这个类的lifecycleEvent方法中(为什么会执行这个方法可以看本博文章的分析)监听了哪些事件:
- /**
- * Process events for an associated Context.
- *
- * @param event The lifecycle event that has occurred
- */
- @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();
- }
- }
与Context的start方法调用相关的事件监听前后顺序为:AFTER_INIT_EVENT(执行init方法)、BEFORE_START_EVENT(执行beforeStart方法)、CONFIGURE_START_EVENT(执行configureStart方法)。
在configureStart方法将直接调用webConfig方法,正是在这个方法中将会解析web.xml文件:
- /**
- * Scan the web.xml files that apply to the web application and merge them
- * using the rules defined in the spec. For the global web.xml files,
- * where there is duplicate configuration, the most specific level wins. ie
- * an application's web.xml takes precedence over the host level or global
- * web.xml file.
- */
- protected void webConfig() {
- /*
- * Anything and everything can override the global and host defaults.
- * This is implemented in two parts
- * - Handle as a web fragment that gets added after everything else so
- * everything else takes priority
- * - Mark Servlets as overridable so SCI configuration can replace
- * configuration from the defaults
- */
- /*
- * The rules for annotation scanning are not as clear-cut as one might
- * think. Tomcat implements the following process:
- * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
- * which Servlet spec version is declared in web.xml. The EG has
- * confirmed this is the expected behaviour.
- * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
- * web.xml is marked as metadata-complete, JARs are still processed
- * for SCIs.
- * - If metadata-complete=true and an absolute ordering is specified,
- * JARs excluded from the ordering are also excluded from the SCI
- * processing.
- * - If an SCI has a @HandlesType annotation then all classes (except
- * those in JARs excluded from an absolute ordering) need to be
- * scanned to check if they match.
- */
- Set<WebXml> defaults = new HashSet<WebXml>();
- defaults.add(getDefaultWebXmlFragment());
- WebXml webXml = createWebXml();
- // Parse context level web.xml
- InputSource contextWebXml = getContextWebXmlSource();
- parseWebXml(contextWebXml, webXml, false);
- ServletContext sContext = context.getServletContext();
- // Ordering is important here
- // Step 1. Identify all the JARs packaged with the application
- // If the JARs have a web-fragment.xml it will be parsed at this
- // point.
- Map<String,WebXml> fragments = processJarsForWebFragments();
- // Step 2. Order the fragments.
- Set<WebXml> orderedFragments = null;
- orderedFragments =
- WebXml.orderWebFragments(webXml, fragments, sContext);
- // Step 3. Look for ServletContainerInitializer implementations
- if (ok) {
- processServletContainerInitializers(orderedFragments);
- }
- if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
- // Step 4. Process /WEB-INF/classes for annotations
- if (ok) {
- // Hack required by Eclipse's "serve modules without
- // publishing" feature since this backs WEB-INF/classes by
- // multiple locations rather than one.
- NamingEnumeration<Binding> listBindings = null;
- try {
- try {
- listBindings = context.getResources().listBindings(
- "/WEB-INF/classes");
- } catch (NameNotFoundException ignore) {
- // Safe to ignore
- }
- while (listBindings != null &&
- listBindings.hasMoreElements()) {
- Binding binding = listBindings.nextElement();
- if (binding.getObject() instanceof FileDirContext) {
- File webInfClassDir = new File(
- ((FileDirContext) binding.getObject()).getDocBase());
- processAnnotationsFile(webInfClassDir, webXml,
- webXml.isMetadataComplete());
- } else {
- String resource =
- "/WEB-INF/classes/" + binding.getName();
- try {
- URL url = sContext.getResource(resource);
- processAnnotationsUrl(url, webXml,
- webXml.isMetadataComplete());
- } catch (MalformedURLException e) {
- log.error(sm.getString(
- "contextConfig.webinfClassesUrl",
- resource), e);
- }
- }
- }
- } catch (NamingException e) {
- log.error(sm.getString(
- "contextConfig.webinfClassesUrl",
- "/WEB-INF/classes"), e);
- }
- }
- // Step 5. Process JARs for annotations - only need to process
- // those fragments we are going to use
- if (ok) {
- processAnnotations(
- orderedFragments, webXml.isMetadataComplete());
- }
- // Cache, if used, is no longer required so clear it
- javaClassCache.clear();
- }
- if (!webXml.isMetadataComplete()) {
- // Step 6. Merge web-fragment.xml files into the main web.xml
- // file.
- if (ok) {
- ok = webXml.merge(orderedFragments);
- }
- // Step 7. Apply global defaults
- // Have to merge defaults before JSP conversion since defaults
- // provide JSP servlet definition.
- webXml.merge(defaults);
- // Step 8. Convert explicitly mentioned jsps to servlets
- if (ok) {
- convertJsps(webXml);
- }
- // Step 9. Apply merged web.xml to Context
- if (ok) {
- webXml.configureContext(context);
- }
- } else {
- webXml.merge(defaults);
- convertJsps(webXml);
- webXml.configureContext(context);
- }
- // Step 9a. Make the merged web.xml available to other
- // components, specifically Jasper, to save those components
- // from having to re-generate it.
- // TODO Use a ServletContainerInitializer for Jasper
- String mergedWebXml = webXml.toXml();
- sContext.setAttribute(
- org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
- mergedWebXml);
- if (context.getLogEffectiveWebXml()) {
- log.info("web.xml:\n" + mergedWebXml);
- }
- // Always need to look for static resources
- // Step 10. Look for static resources packaged in JARs
- if (ok) {
- // Spec does not define an order.
- // Use ordered JARs followed by remaining JARs
- Set<WebXml> resourceJars = new LinkedHashSet<WebXml>();
- if (orderedFragments != null) {
- for (WebXml fragment : orderedFragments) {
- resourceJars.add(fragment);
- }
- }
- for (WebXml fragment : fragments.values()) {
- if (!resourceJars.contains(fragment)) {
- resourceJars.add(fragment);
- }
- }
- processResourceJARs(resourceJars);
- // See also StandardContext.resourcesStart() for
- // WEB-INF/classes/META-INF/resources configuration
- }
- // Step 11. Apply the ServletContainerInitializer config to the
- // context
- if (ok) {
- for (Map.Entry<ServletContainerInitializer,
- Set<Class<?>>> entry :
- initializerClassMap.entrySet()) {
- if (entry.getValue().isEmpty()) {
- context.addServletContainerInitializer(
- entry.getKey(), null);
- } else {
- context.addServletContainerInitializer(
- entry.getKey(), entry.getValue());
- }
- }
- }
- }
这个方法里面做的事情,在英文注释中说的很清楚了,概括起来包括合并Tomcat全局web.xml、当前应用中的web.xml、web-fragment.xml和web应用的注解中的配置信息,并将解析出的各种配置信息(如servlet配置、filter配置等)关联到Context对象中(在上面的代码第140行:webXml.configureContext(context))。
看下configureContext方法:
- /**
- * Configure a {@link Context} using the stored web.xml representation.
- *
- * @param context The context to be configured
- */
- public void configureContext(Context context) {
- // 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(publicId);
- // Everything else in order
- context.setEffectiveMajorVersion(getMajorVersion());
- context.setEffectiveMinorVersion(getMinorVersion());
- for (Entry<String, String> entry : contextParams.entrySet()) {
- context.addParameter(entry.getKey(), entry.getValue());
- }
- context.setDisplayName(displayName);
- context.setDistributable(distributable);
- for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
- context.getNamingResources().addLocalEjb(ejbLocalRef);
- }
- for (ContextEjb ejbRef : ejbRefs.values()) {
- context.getNamingResources().addEjb(ejbRef);
- }
- for (ContextEnvironment environment : envEntries.values()) {
- context.getNamingResources().addEnvironment(environment);
- }
- for (ErrorPage errorPage : errorPages.values()) {
- context.addErrorPage(errorPage);
- }
- for (FilterDef filter : filters.values()) {
- if (filter.getAsyncSupported() == null) {
- filter.setAsyncSupported("false");
- }
- context.addFilterDef(filter);
- }
- for (FilterMap filterMap : filterMaps) {
- context.addFilterMap(filterMap);
- }
- for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
- JspPropertyGroupDescriptor descriptor =
- new ApplicationJspPropertyGroupDescriptor(jspPropertyGroup);
- context.getJspConfigDescriptor().getJspPropertyGroups().add(
- descriptor);
- }
- for (String listener : listeners) {
- context.addApplicationListener(
- new ApplicationListener(listener, false));
- }
- for (Entry<String, String> entry : localeEncodingMappings.entrySet()) {
- context.addLocaleEncodingMappingParameter(entry.getKey(),
- entry.getValue());
- }
- // Prevents IAE
- if (loginConfig != null) {
- context.setLoginConfig(loginConfig);
- }
- for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
- context.getNamingResources().addMessageDestinationRef(mdr);
- }
- // messageDestinations were ignored in Tomcat 6, so ignore here
- context.setIgnoreAnnotations(metadataComplete);
- for (Entry<String, String> entry : mimeMappings.entrySet()) {
- context.addMimeMapping(entry.getKey(), entry.getValue());
- }
- // Name is just used for ordering
- for (ContextResourceEnvRef resource : resourceEnvRefs.values()) {
- context.getNamingResources().addResourceEnvRef(resource);
- }
- for (ContextResource resource : resourceRefs.values()) {
- context.getNamingResources().addResource(resource);
- }
- for (SecurityConstraint constraint : securityConstraints) {
- context.addConstraint(constraint);
- }
- for (String role : securityRoles) {
- context.addSecurityRole(role);
- }
- for (ContextService service : serviceRefs.values()) {
- context.getNamingResources().addService(service);
- }
- for (ServletDef servlet : servlets.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 : servletMappings.entrySet()) {
- context.addServletMapping(entry.getKey(), entry.getValue());
- }
- if (sessionConfig != null) {
- if (sessionConfig.getSessionTimeout() != null) {
- context.setSessionTimeout(
- sessionConfig.getSessionTimeout().intValue());
- }
- SessionCookieConfig scc =
- context.getServletContext().getSessionCookieConfig();
- scc.setName(sessionConfig.getCookieName());
- scc.setDomain(sessionConfig.getCookieDomain());
- scc.setPath(sessionConfig.getCookiePath());
- scc.setComment(sessionConfig.getCookieComment());
- if (sessionConfig.getCookieHttpOnly() != null) {
- scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
- }
- if (sessionConfig.getCookieSecure() != null) {
- scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
- }
- if (sessionConfig.getCookieMaxAge() != null) {
- scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
- }
- if (sessionConfig.getSessionTrackingModes().size() > 0) {
- context.getServletContext().setSessionTrackingModes(
- sessionConfig.getSessionTrackingModes());
- }
- }
- for (Entry<String, String> entry : taglibs.entrySet()) {
- TaglibDescriptor descriptor = new ApplicationTaglibDescriptor(
- entry.getValue(), entry.getKey());
- context.getJspConfigDescriptor().getTaglibs().add(descriptor);
- }
- // Context doesn't use version directly
- for (String welcomeFile : welcomeFiles) {
- /*
- * The following will result in a welcome file of "" so don't add
- * that to the context
- * <welcome-file-list>
- * <welcome-file/>
- * </welcome-file-list>
- */
- if (welcomeFile != null && welcomeFile.length() > 0) {
- context.addWelcomeFile(welcomeFile);
- }
- }
- // Do this last as it depends on servlets
- for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
- String jspServletName = context.findServletMapping("*.jsp");
- if (jspServletName == null) {
- jspServletName = "jsp";
- }
- if (context.findChild(jspServletName) != null) {
- for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
- context.addServletMapping(urlPattern, jspServletName, true);
- }
- } else {
- if(log.isDebugEnabled()) {
- for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
- log.debug("Skiping " + urlPattern + " , no servlet " +
- jspServletName);
- }
- }
- }
- }
- for (Entry<String, String> entry : postConstructMethods.entrySet()) {
- context.addPostConstructMethod(entry.getKey(), entry.getValue());
- }
- for (Entry<String, String> entry : preDestroyMethods.entrySet()) {
- context.addPreDestroyMethod(entry.getKey(), entry.getValue());
- }
- }
可以看到里面对context调用了各种set、add方法,从而将web.xml中的各种配置信息与表示一个web应用的context对象关联起来。