为什么关心Tomcat中一个web应用的加载过程?在前面的文章中看过多次Tomcat的组件结构图,这里再贴出来回顾一下:
之前的《Tomcat7启动分析》系列文章中看到Tomcat启动的时候将会解析server.xml,根据里面所配置的各个节点信息逐一初始化和启动相应组件(即分别调用它们的init和start方法),但浏览一下Tomcat7源码中的server.xml的内容,里面对应上图中的已经默认配置的各级组件包括Server、Service、Engine、Connector、Host、Valve。上图中的Context组件实际就是我们通常所说的一个web应用,有趣的是在server.xml中并没有配置该组件,而我们默认启动的时候实际上已经有好几个web应用可以访问了:
这个到底是怎么回事?
看过前面的《Tomcat7中一次请求处理的前世今生》系列文章的人应该知道,浏览器一次请求送到Tomcat服务器之后,最终会根据浏览器中的url路径找到相应的实际要访问的web应用的context对象(默认即org.apache.catalina.core.StandardContext类的实例)。比如访问的url为http://localhost:8080/,那么将会送到上图ROOT文件夹表示的web应用中,访问的url为http://localhost:8080/docs,那么将访问docs文件夹表示的web应用。所以能够猜到的是在Tomcat启动完成后,必定容器内部已经构造好了表示相应web应用的各个context对象。
本文就对这个问题一探究竟。在《Tomcat7服务器关闭原理》的开头提到,默认的配置下Tomcat启动完之后会看到后台实际上总共有6个线程在运行:
前面的几篇文章中涉及了main、http-bio-8080-Acceptor-0、http-bio-8080-AsyncTimeout、ajp-bio-8009-Acceptor-0、ajp-bio-8009-AsyncTimeout,已经谈到了这些线程的作用,它们是如何产生并响应请求的。但有一个线程没有说,即ContainerBackgroundProcessor[StandardEngine[Catalina]],而本文要解答的问题奥秘就在这个线程之中。
先看看这个线程是如何产生的,其实从命名就可以看出一些端倪,它叫做容器后台处理器,并且跟StandardEngine关联起来,它的产生于作用也同样如此。
Tomcat7中所有的默认容器组件(StandardEngine、StandardHost、StandardContext、StandardWrapper)都会继承父类org.apache.catalina.core.ContainerBase,在这些容器组件启动时将会调用自己内部的startInternal方法,在该方法内部一般会调用父类的startInternal方法(StandardContext类的实现除外),比如org.apache.catalina.core.StandardEngine类中的startInternal方法:
@Override protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if(log.isInfoEnabled()) log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo()); // Standard container startup super.startInternal(); }
最后的super.startInternal()即调用父类org.apache.catalina.core.ContainerBase的startInternal方法,在该方法最后:
// Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); setState(LifecycleState.STARTING); // Start our thread threadStart();
第6行设置了LifecycleState.STARTING状态(这样将向容器发布一个Lifecycle.START_EVENT事件),这一行的作用本文后面会提到,暂且按下不表。第9行调用threadStart方法,看看threadStart方法的代码:
// -------------------- Background Thread -------------------- /** * Start the background thread that will periodically check for * session timeouts. */ protected void threadStart() { if (thread != null) return; if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); }
这里可以看到如果两个前置校验条件通过的话将会启动一个线程,并且线程的名字即以" ContainerBackgroundProcessor[ "开头,线程名字后面取的是对象的toString方法,以StandardEngine为例,看看org.apache.catalina.core.StandardEngine的toString方法实现:
/** * Return a String representation of this component. */ @Override public String toString() { StringBuilder sb = new StringBuilder("StandardEngine["); sb.append(getName()); sb.append("]"); return (sb.toString()); }
以上解释了这个后台线程的来历。
但这里有一个问题,既然StandardEngine、StandardHost都会调用super.startInternal()方法,按默认配置,后台理应产生两个后台线程,实际为什么只有一个?
回到org.apache.catalina.core.ContainerBase的threadStart方法,在启动线程代码之前有两个校验条件:
if (thread != null) return; if (backgroundProcessorDelay <= 0) return;
容器组件对象初始化时thread为null,backgroundProcessorDelay是-1
/** * The background thread. */ private Thread thread = null; /** * The processor delay for this component. */ protected int backgroundProcessorDelay = -1;
但org.apache.catalina.core.StandardEngine在其自身构造函数中做了一点修改:
public StandardEngine() { super(); pipeline.setBasic(new StandardEngineValve()); /* Set the jmvRoute using the system property jvmRoute */ try { setJvmRoute(System.getProperty("jvmRoute")); } catch(Exception ex) { log.warn(sm.getString("standardEngine.jvmRouteFail")); } // By default, the engine will hold the reloading thread backgroundProcessorDelay = 10; }
构造函数最后将父类的backgroundProcessorDelay的值由-1改成了10,所以Tomcat启动解析xml时碰到一个Engine节点就会对应产生一个后台处理线程。
讲完了这个后台处理线程的产生,看看这个线程所作的事情,再看下这个线程的启动代码:
thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start();
所以这个线程将会执行ContainerBase的内部类ContainerBackgroundProcessor的run方法,看下ContainerBackgroundProcessor的全部实现代码:
/** * Private thread class to invoke the backgroundProcess method * of this container and its children after a fixed delay. */ protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // Ignore } if (!threadDone) { Container parent = (Container) getMappingObject(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (parent.getLoader() != null) { cl = parent.getLoader().getClassLoader(); } processChildren(parent, cl); } } } protected void processChildren(Container container, ClassLoader cl) { try { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } container.backgroundProcess(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error("Exception invoking periodic operation: ", t); } finally { Thread.currentThread().setContextClassLoader(cl); } Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i], cl); } } } }
在它的run方法暂停一段时间之后会调用processChildren方法,而processChildren方法做了两件事,一是调用容器组件自身的backgroundProcess方法,而是取出该容器组件的所有子容器组件并调用它们的processChildren方法。归结起来这个线程的实现就是定期通过递归的方式调用当前容器及其所有子容器的backgroundProcess方法。
而这个backgroundProcess方法在ContainerBase内部已经给出了实现:
@Override public void backgroundProcess() { if (!getState().isAvailable()) return; if (cluster != null) { try { cluster.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e); } } if (loader != null) { try { loader.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e); } } if (manager != null) { try { manager.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e); } } Realm realm = getRealmInternal(); if (realm != null) { try { realm.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e); } } Valve current = pipeline.getFirst(); while (current != null) { try { current.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e); } current = current.getNext(); } fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }
这段代码就不一一解释了,概括起来说就是逐个调用与容器相关其它内部组件的backgroundProcess方法。最后注册一个Lifecycle.PERIODIC_EVENT事件。
上面就是Tomcat7的后台处理线程所作的事情的概述,在Tomcat的早期版本中有一些后台处理的事情原来是在各个组件内部分别自定义一个线程并启动,在Tomcat5中改成了所有后台处理共享同一线程的方式。
回到本文要解答的问题,web应用如何加载到容器中的?在ContainerBase类的backgroundProcess方法的最后:
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
向容器注册了一个PERIODIC_EVENT事件。前面说道默认的ContainerBackgroundProcessor[StandardEngine[Catalina]]线程会定期(默认为10秒)执行Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法,所以也会定期向Host组件发布一个PERIODIC_EVENT事件,这里看下StandardHost都会关联的一个监听器org.apache.catalina.startup.HostConfig(
在Tomcat启动解析xml时org.apache.catalina.startup.Catalina类的386行:digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"))
在HostRuleSet类的addRuleInstances方法中:
public void addRuleInstances(Digester digester) { digester.addObjectCreate(prefix + "Host", "org.apache.catalina.core.StandardHost", "className"); digester.addSetProperties(prefix + "Host"); digester.addRule(prefix + "Host", new CopyParentClassLoaderRule()); digester.addRule(prefix + "Host", new LifecycleListenerRule ("org.apache.catalina.startup.HostConfig", "hostConfigClass")); digester.addSetNext(prefix + "Host", "addChild", "org.apache.catalina.Container");
第9到12行看到,所有Host节点都会添加一个org.apache.catalina.startup.HostConfig对象作为org.apache.catalina.core.StandardHost对象的监听器
),在HostConfig的lifecycleEvent方法中可以看到如果Host组件收到了Lifecycle.PERIODIC_EVENT事件的发布所作出的响应(如果对Tomcat7的Lifecycle机制不清楚可以看下《Tomcat7启动分析(五)Lifecycle机制和实现原理》):
public void lifecycleEvent(LifecycleEvent event) { // Identify the host we are associated with try { host = (Host) event.getLifecycle(); if (host instanceof StandardHost) { setCopyXML(((StandardHost) host).isCopyXML()); setDeployXML(((StandardHost) host).isDeployXML()); setUnpackWARs(((StandardHost) host).isUnpackWARs()); } } catch (ClassCastException e) { log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) { check(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { stop(); } }
第17行,如果发布的事件是PERIODIC_EVENT将会执行check方法。第19行,如果发布的事件是START_EVENT则执行start方法。check方法和start方法最后都会调用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); }
这里即各种不同方式发布web应用的代码。
本文前面提到默认情况下组件启动的时候会发布一个Lifecycle.START_EVENT事件(在org.apache.catalina.core.ContainerBase类的startInternal方法倒数第二行),回到HostConfig的lifecycleEvent方法中,所以默认启动时将会执行HostConfig的start方法,在该方法的最后:
if (host.getDeployOnStartup()) deployApps();
因为默认配置host.getDeployOnStartup()返回true,这样容器就会在启动的时候直接加载相应的web应用。
当然,如果在server.xml中Host节点的deployOnStartup属性设置为false,则容器启动时不会加载应用,启动完之后不能立即提供web应用的服务。但因为有上面提到的后台处理线程在运行,会定期执行HostConfig的check方法:
/** * Check status of all webapps. */ protected void check() { if (host.getAutoDeploy()) { // Check for resources modification to trigger redeployment DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) checkResources(apps[i]); } // Check for old versions of applications that can now be undeployed if (host.getUndeployOldVersions()) { checkUndeploy(); } // Hotdeploy applications deployApps(); } }
如果Host节点的autoDeploy属性是true(默认设置即为true),可以看到check方法最后同样会加载web应用。
相关推荐
Tomcat 中 web.xml 文件是 Web 应用的核心配置文件,负责管理 Web 应用的生命周期、Servlet 的加载顺序、Filter 的配置等。下面对 web.xml 文件中的重要元素进行详细解释。 context-param 元素 context-param 元素...
2. **Web应用部署**:讲解如何在Tomcat中部署WAR文件或通过Context配置文件部署应用程序。 3. **Servlet生命周期**:详述Servlet的初始化、服务和销毁过程,以及如何使用Servlet API进行控制。 4. **JSP基础**:...
9. **集成其他服务**:Tomcat7可以与其他Java应用服务器或数据库进行集成,例如与Spring Boot、Hibernate等框架配合,构建复杂的Web应用程序。 10. **社区支持**:作为开源项目,Tomcat拥有活跃的社区支持,用户...
《Tomcat与Java.Web开发技术详解》是一本深入探讨Java Web应用服务器——Tomcat以及相关开发技术的专业书籍。光盘附带的"sourcecode1"文件可能是书中示例代码或练习项目的源码,旨在帮助读者更好地理解和实践所学...
Apache Tomcat 7是一款广泛应用的开源Java Servlet容器,它实现了Java EE Web应用程序规范,特别是Servlet 3.0、JSP 2.2和EL 2.2标准。这个"tomcat7资源包"包含了适用于不同操作系统平台的版本,确保用户无论在...
在IT行业中,Tomcat是一款广泛使用的开源Web应用服务器,它主要负责运行基于Java Servlet和JavaServer Pages(JSP)的应用程序。本主题“Tomcat与Java.Web开发技术详解源代码”将深入探讨Tomcat的工作原理、配置方法...
在Tomcat中,Servlet是web应用的核心组件,用于处理业务逻辑。 2. **JavaServer Pages (JSP)**:JSP是一种基于Java的技术,用于创建动态网页。它允许开发者将静态内容(HTML、CSS、JavaScript)与动态Java代码混合...
Tomcat作为Servlet容器,负责加载、执行和管理Servlet,使得开发者可以使用Java语言构建动态Web应用。 3. **JSP (JavaServer Pages)**:JSP是Java EE平台的一部分,它允许开发人员在HTML代码中嵌入Java代码,以生成...
- **Context加载**: 每个Web应用对应一个Context,负责加载Web应用的`WEB-INF/web.xml`配置文件,并创建Servlet实例。 2. **请求处理机制** - **接收请求**: 当Connector接收到HTTP请求后,通过ProtocolHandler...
【标题】:“Tomcat7安装版” 在Java Web开发领域,Tomcat是一个...总的来说,"Tomcat7安装版"涵盖了Java Web开发的基础环境,通过深入了解和掌握其工作原理和配置,开发者可以构建和维护高效、安全的Web应用程序。
在Java Web开发中,`org.springframework.web.context.ContextLoaderListener` 是Spring框架的一部分,它负责初始化一个Web应用程序的Spring上下文。这个监听器是基于Servlet容器(如Tomcat、Jetty等)的,当Web应用...
【描述】:Tomcat是Apache软件基金会下的Jakarta项目中的一个核心项目,是一个开源的轻量级Web应用服务器,主要用于执行Java Servlet和JavaServer Pages(JSP)技术。Java.Web是指基于Java技术的Web开发体系,包括...
案例中搭建Tomcat服务器的过程是一个重要的知识点,主要包括安装Tomcat软件、配置环境变量、部署Web应用、启动和测试Tomcat服务器。在部署Web应用时,通常需要将应用的WAR包放置到Tomcat的webapps目录下,Tomcat会...
【Tomcat原理与实战】 Tomcat是一个开源的Java Servlet容器,由Apache软件基金会的Jakarta项目...熟悉这些知识,对于开发和运维Java Web应用至关重要,能够帮助我们更好地利用Tomcat构建高效、稳定、可扩展的Web服务。
Servlet是Java EE标准中的一部分,用于构建动态Web应用程序。Tomcat作为Servlet容器,它遵循Servlet规范来运行和管理Servlet。在Servlet中,有以下几个关键概念: 1. **Listener(监听器)** 监听器是Java EE中的...
Apache Tomcat 7 是一个广泛使用的开源Java Servlet容器,它...通过深入学习和理解Apache Tomcat 7的源码,开发者不仅可以解决实际问题,还能提升对Web服务器原理的理解,为构建高效、稳定的Java Web应用打下坚实基础。
在Java开发中,创建Web服务是一种常见的需求,Eclipse作为流行的Java IDE,配合CXF(一个开源的Java SOAP和RESTful Web服务框架)以及Tomcat(流行的Java应用服务器),可以方便地实现这一目标。以下是使用Eclipse、...
Tomcat是Apache软件基金会的Jakarta项目中的一个核心项目,是一款开源、免费的Web服务器和应用服务器,专门用于运行Java Servlet和JavaServer Pages(JSP)的容器。它以其轻量级、高效能和易用性而受到广大开发者的...
《孙卫琴 Tomcat Web开发 源代码》是一份深度剖析Tomcat源码的教程,旨在帮助开发者深入了解Web应用程序的运行机制。Tomcat作为Apache软件基金会的开源项目,是Java Servlet和JavaServer Pages(JSP)的标准实现,...
在IT行业中,Tomcat是一个广泛使用的轻量级Java应用服务器,尤其在开发和部署Web应用程序时。本篇文章将深入解析“简单的tomcat实现1”,帮助读者理解Tomcat的核心概念、工作原理以及如何手写Tomcat的基本组件。 ...