tomcat系列(二) tomcat启动流程

117 阅读6分钟

在上一章我们讲到了,tomcat是怎么用生命周期来统一管理它底下这么多容器的各种操作,但是关于tomcat具体的启动流程我们只是在文章的末尾,简单的提了一句。在这这一章我会详细的讲解一下,tomcat的启动流程和配置文件是怎么样 在什么时候生效的。

我们看一下tomcat的启动代码 `

synchronized (daemonLock) {
    if (daemon == null) {
        // Don't set daemon until init() has completed
        // init()完成之前不要设置daemon
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 初始化操作 1 初始化tomcat核心类加载器 2 通过反射实例化核心类Catalina.class
            bootstrap.init(); 
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        // 将当前
        daemon = bootstrap;
    } else {
        // When running as a service the call to stop will be on a new
        // thread so make sure the correct class loader is used to
        // prevent a range of class not found exceptions.
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
}

try {
    String command = "start";
    if (args.length > 0) {
        command = args[args.length - 1];
    }

    // 通过命令行确认执行对应的生命周期方法
    if (command.equals("startd")) {
        args[args.length - 1] = "start";
        daemon.load(args); //
        daemon.start();
    } else if (command.equals("stopd")) {
        args[args.length - 1] = "stop";
        daemon.stop();
    } else if (command.equals("start")) {
        daemon.setAwait(true);
        daemon.load(args);
        daemon.start();
        if (null == daemon.getServer()) {
            System.exit(1);
        }
    } else if (command.equals("stop")) {
        daemon.stopServer(args);
    } else if (command.equals("configtest")) {
        daemon.load(args);
        if (null == daemon.getServer()) {
            System.exit(1);
        }
        System.exit(0);
    } else {
        log.warn("Bootstrap: command "" + command + "" does not exist.");
    }
} catch (Throwable t) {
    // Unwrap the Exception for clearer error reporting
    if (t instanceof InvocationTargetException &&
            t.getCause() != null) {
        t = t.getCause();
    }
    handleThrowable(t);
    t.printStackTrace();
    System.exit(1);
}

main方法中 主要做了以下两步

  • 初始化tomcat中的顶层类加载器(关于tomcat类加载器的问题会在后续篇幅中进行讲解),然后通过反射的方式实例化Catalina类
  • 将当前bootstrap对象赋值给deamon属性 方便后续操作
  • 根据命令行从顶层容器开始执行对应的start方法
bootstrap.load()

image.png 在load方法中会调用我们刚生成的Catalina对象的load方法

standardServer.init()
if (loaded) {
    return; // 只能被加载一次
}
loaded = true;

long t1 = System.nanoTime();

initDirs(); // 废弃的方法

// Before digester - it may be needed
initNaming(); // 和JNDI 相关的内容 忽略

// Create and execute our Digester
// 创建并且执行我们的 Digester 对象  Server.xml
Digester digester = createStartDigester();

InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
    try {
        file = configFile();
        inputStream = new FileInputStream(file);
        inputSource = new InputSource(file.toURI().toURL().toString());
    } catch (Exception e) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("catalina.configFail", file), e);
        }
    }
    if (inputStream == null) {
        try {
            inputStream = getClass().getClassLoader()
                .getResourceAsStream(getConfigFile());
            inputSource = new InputSource
                (getClass().getClassLoader()
                 .getResource(getConfigFile()).toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail",
                        getConfigFile()), e);
            }
        }
    }

    // This should be included in catalina.jar
    // Alternative: don't bother with xml, just create it manually.
    if (inputStream == null) {
        try {
            inputStream = getClass().getClassLoader()
                    .getResourceAsStream("server-embed.xml");
            inputSource = new InputSource
            (getClass().getClassLoader()
                    .getResource("server-embed.xml").toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail",
                        "server-embed.xml"), e);
            }
        }
    }


    if (inputStream == null || inputSource == null) {
        if  (file == null) {
            log.warn(sm.getString("catalina.configFail",
                    getConfigFile() + "] or [server-embed.xml]"));
        } else {
            log.warn(sm.getString("catalina.configFail",
                    file.getAbsolutePath()));
            if (file.exists() && !file.canRead()) {
                log.warn("Permissions incorrect, read permission is not allowed on the file.");
            }
        }
        return;
    }

    try {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        // 解析server.xml  获取对应server service connector属性信息
        digester.parse(inputSource);
    } catch (SAXParseException spe) {
        log.warn("Catalina.start using " + getConfigFile() + ": " +
                spe.getMessage());
        return;
    } catch (Exception e) {
        log.warn("Catalina.start using " + getConfigFile() + ": " , e);
        return;
    }
} finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            // Ignore
        }
    }
}

// 设置server对应的Catalina属性
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

// Stream redirection
initStreams();

// 启动Server 服务
try {
    getServer().init(); // 完成 Server  Service Engine Connector等组件的init操作
} catch (LifecycleException e) {
    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
        throw new java.lang.Error(e);
    } else {
        log.error("Catalina.start", e);
    }
}

long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
    log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}

在Catalina.load中分为一下几个步骤

  • 解析server.xml文件 获取对应的配置信息 并设置standardServer到Catalina的server属性中(这一步不详细讲解,因为本人也没详细看),这是所有生命周期方法的起点
  • 通过配置的service,遍历执行对应service的init方法
standardServer.init()

从这一步开始就正式进入我们的生命周期了,通过上一章的知识我们知道,init方法本质是一个模板方法,具体的子类实现的是initInternal方法。这一点后面不会再次提到,如果有不清楚生命周期的同学可以看上一章的知识点

super.initInternal();

onameStringCache = register(new StringCache(), "type=StringCache");

MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");

globalNamingResources.init();

if (getCatalina() != null) {
    ClassLoader cl = getCatalina().getParentClassLoader();
    while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
        if (cl instanceof URLClassLoader) {
            URL[] urls = ((URLClassLoader) cl).getURLs();
            for (URL url : urls) {
                if (url.getProtocol().equals("file")) {
                    try {
                        File f = new File (url.toURI());
                        if (f.isFile() &&
                                f.getName().endsWith(".jar")) {
                            ExtensionValidator.addSystemResource(f);
                        }
                    } catch (URISyntaxException | IOException e) {
                        // Ignore
                    }
                }
            }
        }
        cl = cl.getParent();
    }
}
// 开始初始化server底下的service
for (Service service : services) {
    service.init();
}
  • 设置生命周期模板方法工厂
  • 设置全局资源命名空间
  • 遍历执行server下所有service的init方法
standardService.init()
super.initInternal();

// 初始化engine
if (engine != null) {
    engine.init();
}

// 初始化连接池
for (Executor executor : findExecutors()) {
    if (executor instanceof JmxEnabled) {
        ((JmxEnabled) executor).setDomain(getDomain());
    }
    executor.init();
}

// 初始化mapper监听器
mapperListener.init();

// 初始话连接器
synchronized (connectorsLock) {
    for (Connector connector : connectors) {
        try {
            connector.init();
        } catch (Exception e) {
            String message = sm.getString(
                    "standardService.connector.initFailed", connector);
            log.error(message, e);

            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new LifecycleException(message);
            }
        }
    }
}
  • 执行engine的init方法
  • 初始化连接线程池
  • 初始化监听器
  • 初始化连接器(连接器这块会在后续专门的章节进行讲解)

到了这里我们已经过了tomcat load方法的流程.但是小伙伴会说为什么这里到了engine就结束了 后面的host context wrapper呢。这也就是我们要讲的下一个方法,Catalina.start(),因为执行流程的思路和load的方法是一致的。所以后面的内容会直接上代码

standardServer.startInternal()
// 发布  configure_start 事件,触发对应的监听器
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING); // 子类中设置状态为 启动中

globalNamingResources.start(); // JNDI 的操作 忽略

// Start our defined Services
synchronized (servicesLock) {
    for (Service service : services) {
        service.start(); // 分别启动各个service 服务
    }
}
standardService.startInternal()
if(log.isInfoEnabled()) {
    log.info(sm.getString("standardService.start.name", this.name));
}
setState(LifecycleState.STARTING); // service 组件的状态也更新为 Starting 状态

// Start our defined Container first
if (engine != null) {
    synchronized (engine) {
        engine.start(); // 启动Engine
    }
}

synchronized (executors) {
    for (Executor executor: executors) {
        executor.start(); // 启动 Executor
    }
}

mapperListener.start();

// Start our defined Connectors second
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) {
            log.error(sm.getString(
                    "standardService.connector.startFailed",
                    connector), e);
        }
    }
}
standardEngine.startInternal()
// Log our server identification information
if (log.isInfoEnabled()) {
    log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}

// Standard container startup
super.startInternal();
super.startInternal()
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
    ((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
    ((Lifecycle) realm).start();
}

// 异步启动子容器 如果有的话
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
    results.add(startStopExecutor.submit(new StartChild(child)));
}

MultiThrowable multiThrowable = null;

for (Future<Void> result : results) {
    try {
        result.get();
    } catch (Throwable e) {
        log.error(sm.getString("containerBase.threadedStartFailed"), e);
        if (multiThrowable == null) {
            multiThrowable = new MultiThrowable();
        }
        multiThrowable.add(e);
    }

}
if (multiThrowable != null) {
    throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
            multiThrowable.getThrowable());
}

// 启动容器中的管道
if (pipeline instanceof Lifecycle) {
    ((Lifecycle) pipeline).start();
}

setState(LifecycleState.STARTING);

// 执行容器中需要后台执行的方法
threadStart();
  • 启动对应属性的start方法
  • 异步执行子容器的start方法
  • 启动管道
  • 启动容器异步线程去执行本容器的后台方法

到了这里关于tomcat本身的启动流程就基本完了,剩下关于engine往下的操作,包括tomcat的自定义类加载器,连接器的初始化和启动,管道的初始化和启动,tomcat处理请求的流程。会在后续的文章里面,有专门的章节来进行讲解。

总结

在这一章里,我们详细的看了tomcat的初始化方法和启动方法,并且和生命周期串联在了一起。但是有很多内容,我是简单带过或者直接跳过。这边作者给大家道个歉,因为实力有限,有一下东西我也不敢妄下定论以免误人子弟。如果有兴趣的小伙伴自己阅读了文章之外的代码,或者在我的文章中有理解有误或者用词不当的地方。欢迎与我进行讨论和勘误。