在上一篇文章中, 我们分析了 tomcat 的初始化过程, 是由 Bootstrap 反射调用 Catalina 的 load 方法完成 tomcat 的初始化, 包括 server.xml 的解析, 实例化各大组件, 初始化组件等逻辑. 那么 tomcat 又是如何启动 webapp 应用, 又是如何加载应用程序的 ServletContextListener, 以及 Servlet 呢? 我们将在这篇文章进行分析
我们先来看下整体的启动逻辑, tomcat 由上往下, 挨个启动各个组件:
我们接着上一篇文章来分析, 上一篇文章我们分析完了 Catalina.load(), 这篇文章来看看 daemon.start();
- Bootstrap
- daemon.start()
启动过程和初始化一样, 由 Bootstrap 反射调用 Catalina 的 start 方法
- public void start()
- throws Exception {
- if( catalinaDaemon==null ) init();
- Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
- method.invoke(catalinaDaemon, (Object [])null);
- }
- Catalina
- public void start() {
- if (getServer() == null) {
- load();
- }
- if (getServer() == null) {
- log.fatal("Cannot start server. Server instance is not configured.");
- return;
- }
- long t1 = System.nanoTime();
- // Start the new server
- try {
- // 调用 Server 的 start 方法, 启动 Server 组件
- getServer().start();
- } catch (LifecycleException e) {
- log.fatal(sm.getString("catalina.serverStartFail"), e);
- try {
- getServer().destroy();
- } catch (LifecycleException e1) {
- log.debug("destroy() failed for failed Server", e1);
- }
- return;
- }
- long t2 = System.nanoTime();
- if(log.isInfoEnabled()) {
- log.info("Server startup in" + ((t2 - t1) / 1000000) + "ms");
- }
- // Register shutdown hook
- // 注册勾子, 用于安全关闭 tomcat
- if (useShutdownHook) {
- if (shutdownHook == null) {
- shutdownHook = new CatalinaShutdownHook();
- }
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- // If JULI is being used, disable JULI's shutdown hook since
- // shutdown hooks run in parallel and log messages may be lost
- // if JULI's hook completes before the CatalinaShutdownHook()
- LogManager logManager = LogManager.getLogManager();
- if (logManager instanceof ClassLoaderLogManager) {
- ((ClassLoaderLogManager) logManager).setUseShutdownHook(
- false);
- }
- }
- // Bootstrap 中会设置 await 为 true, 其目的在于让 tomcat 在 shutdown 端口阻塞监听关闭命令
- if (await) {
- await();
- stop();
- }
- }
- Server
在前面的 Lifecycle 文章中, 我们介绍了 StandardServer 重写了 startInternal 方法, 完成自己的逻辑
- StandardServer.startInternal
- protected void startInternal() throws LifecycleException {
- fireLifecycleEvent(CONFIGURE_START_EVENT, null);
- setState(LifecycleState.STARTING);
- globalNamingResources.start();
- // Start our defined Services
- synchronized (servicesLock) {
- for (int i = 0; i <services.length; i++) {
- services[i].start();
- }
- }
- }
先是由 LifecycleBase 统一发出 STARTING_PREP 事件, StandardServer 额外还会发出 CONFIGURE_START_EVENT,STARTING 事件, 用于通知 LifecycleListener 在启动前做一些准备工作, 比如 NamingContextListener 会处理 CONFIGURE_START_EVENT 事件, 实例化 tomcat 相关的上下文, 以及 ContextResource 资源
接着, 启动 Service 组件, 这一块的逻辑将在下面进行详细分析, 最后由 LifecycleBase 发出 STARTED 事件, 完成 start
Service
StandardService 的 start 代码如下所示:
1. 启动 Engine,Engine 的 child 容器都会被启动, webapp 的部署会在这个步骤完成;
2. 启动 Executor, 这是 tomcat 用 Lifecycle 封装的线程池, 继承至 java.util.concurrent.Executor 以及 tomcat 的 Lifecycle 接口
3. 启动 Connector 组件, 由 Connector 完成 Endpoint 的启动, 这个时候意味着 tomcat 可以对外提供请求服务了
- StandardService.startInternal
- protected void startInternal() throws LifecycleException {
- setState(LifecycleState.STARTING);
- // 启动 Engine
- if (engine != null) {
- synchronized (engine) {
- engine.start();
- }
- }
- // 启动 Executor 线程池
- synchronized (executors) {
- for (Executor executor: executors) {
- executor.start();
- }
- }
- // 启动 MapperListener
- mapperListener.start();
- // 启动 Connector
- synchronized (connectorsLock) {
- for (Connector connector: connectors) {
- try {
- // If it has already failed, don't try and start it
- if (connector.getState() != LifecycleState.FAILED) {
- connector.start();
- }
- } catch (Exception e) {
- // logger......
- }
- }
- }
- }
- Engine
Engine 的标准实现为 org.apache.catalina.core.StandardEngine. 我们先来看看构造函数. 其主要职责为: 使用默认的基础阀门创建标准 Engine 组件.
- /**
- * Create a new StandardEngine component with the default basic Valve.
- */
- 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;
- }
我们来看看 StandardEngine.startInternal
- 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();
- }
StandardEngine,StandardHost,StandardContext,StandardWrapper 各个容器存在父子关系, 一个父容器包含多个子容器, 并且一个子容器对应一个父容器. Engine 是顶层父容器, 它不存在父容器. 各个组件的包含关系如下图所示, 默认情况下, StandardEngine 只有一个子容器 StandardHost, 一个 StandardContext 对应一个 webapp 应用, 而一个 StandardWrapper 对应一个 webapp 里面的一个 Servlet
StandardEngine,StandardHost,StandardContext,StandardWrapper 都是继承至 ContainerBase, 各个容器的启动, 都是由父容器调用子容器的 start 方法, 也就是说由 StandardEngine 启动 StandardHost, 再 StandardHost 启动 StandardContext, 以此类推.
由于它们都是继续至 ContainerBase, 当调用 start 启动 Container 容器时, 首先会执行 ContainerBase 的 start 方法, 它会寻找子容器, 并且在线程池中启动子容器, StandardEngine 也不例外.
ContainerBase
ContainerBase 的 startInternal 方法如下所示, 主要分为以下 3 个步骤:
1. 启动子容器
2. 启动 Pipeline, 并且发出 STARTING 事件
3. 如果 backgroundProcessorDelay 参数 >= 0, 则开启 ContainerBackgroundProcessor 线程, 用于调用子容器的 backgroundProcess
- protected synchronized void startInternal() throws LifecycleException {
- // 省略若干代码......
- // 把子容器的启动步骤放在线程中处理, 默认情况下线程池只有一个线程处理任务队列
- Container children[] = findChildren();
- List<Future<Void>> results = new ArrayList<>();
- for (int i = 0; i <children.length; i++) {
- results.add(startStopExecutor.submit(new StartChild(children[i])));
- }
- // 阻塞当前线程, 直到子容器 start 完成
- boolean fail = false;
- for (Future<Void> result : results) {
- try {
- result.get();
- } catch (Exception e) {
- log.error(sm.getString("containerBase.threadedStartFailed"), e);
- fail = true;
- }
- }
- // 启用 Pipeline
- if (pipeline instanceof Lifecycle)
- ((Lifecycle) pipeline).start();
- setState(LifecycleState.STARTING);
- // 开启 ContainerBackgroundProcessor 线程用于调用子容器的 backgroundProcess 方法, 默认情况下 backgroundProcessorDelay=-1, 不会启用该线程
- threadStart();
- }
ContainerBase 会把 StartChild 任务丢给线程池处理, 得到 Future, 并且会遍历所有的 Future 进行阻塞 result.get(), 这个操作是将异步启动转同步, 子容器启动完成才会继续运行. 我们再来看看 submit 到线程池的 StartChild 任务, 它实现了 java.util.concurrent.Callable 接口, 在 call 里面完成子容器的 start 动作
- private static class StartChild implements Callable<Void> {
- private Container child;
- public StartChild(Container child) {
- this.child = child;
- }
- @Override
- public Void call() throws LifecycleException {
- child.start();
- return null;
- }
- }
启动 Pipeline
默认使用 StandardPipeline 实现类, 它也是一个 Lifecycle. 在容器启动的时候, StandardPipeline 会遍历 Valve 链表, 如果 Valve 是 Lifecycle 的子类, 则会调用其 start 方法启动 Valve 组件, 代码如下
- public class StandardPipeline extends LifecycleBase
- implements Pipeline, Contained {
- // 省略若干代码......
- protected synchronized void startInternal() throws LifecycleException {
- Valve current = first;
- if (current == null) {
- current = basic;
- }
- while (current != null) {
- if (current instanceof Lifecycle)
- ((Lifecycle) current).start();
- current = current.getNext();
- }
- setState(LifecycleState.STARTING);
- }
- }
- Host
分析 Host 的时候, 我们从 Host 的构造函数入手, 该方法主要是设置基础阀门.
- public StandardHost() {
- super();
- pipeline.setBasic(new StandardHostValve());
- }
- StandardEngine.startInternal
- protected synchronized void startInternal() throws LifecycleException {
- // errorValve 默认使用 org.apache.catalina.valves.ErrorReportValve
- String errorValve = getErrorReportValveClass();
- if ((errorValve != null) && (!errorValve.equals(""))) {
- try {
- boolean found = false;
- // 如果所有的阀门中已经存在这个实例, 则不进行处理, 否则添加到 Pipeline 中
- Valve[] valves = getPipeline().getValves();
- for (Valve valve : valves) {
- if (errorValve.equals(valve.getClass().getName())) {
- found = true;
- break;
- }
- }
- // 如果未找到则添加到 Pipeline 中, 注意是添加到 basic valve 的前面
- // 默认情况下, first valve 是 AccessLogValve,basic 是 StandardHostValve
- if(!found) {
- Valve valve =
- (Valve) Class.forName(errorValve).getConstructor().newInstance();
- getPipeline().addValve(valve);
- }
- } catch (Throwable t) {
- // 处理异常, 省略......
- }
- }
- // 调用父类 ContainerBase, 完成统一的启动动作
- super.startInternal();
- }
StandardHost Pipeline 包含的 Valve 组件:
- basic:org.apache.catalina.core.StandardHostValve
- first:org.apache.catalina.valves.AccessLogValve
需要注意的是, 在往 Pipeline 中添加 Valve 阀门时, 是添加到 first 后面, basic 前面
Context
接下来我们分析一下 Context 的实现 org.apache.catalina.core.StandardContext.
先来看看构造方法, 该方法用于设置 Context.pipeline 的基础阀门.
- public StandardContext() {
- super();
- pipeline.setBasic(new StandardContextValve());
- broadcaster = new NotificationBroadcasterSupport();
- // Set defaults
- if (!Globals.STRICT_SERVLET_COMPLIANCE) {
- // Strict servlet compliance requires all extension mapped servlets
- // to be checked against welcome files
- resourceOnlyServlets.add("jsp");
- }
- }
启动方法和上面的容器启动方法类似, 我们就不再赘述了
Wrapper
Wrapper 是一个 Servlet 的包装, 我们先来看看构造方法. 主要作用就是设置基础阀门 StandardWrapperValve.
- public StandardWrapper() {
- super();
- swValve=new StandardWrapperValve();
- pipeline.setBasic(swValve);
- broadcaster = new NotificationBroadcasterSupport();
- }
这里每个容器中的 pipeline 设置的 StandardEngineValve,StandardHostValve,StandardContextValve,StandardWrapperValve 是有大用处的, 后面我们会在 Http 请求过程中详细讲解.
总结
至此, 整个启动过程便告一段落. 整个启动过程程, 由 parent 组件控制 child 组件的启动, 一层层往下传递, 直到最后全部启动完成.
来源: https://www.cnblogs.com/java-chen-hao/p/11344993.html