1. 首页
  2. Tomcat 系列

Tomcat 第三篇:总体架构设计

Tomcat 第三篇:总体架构设计

Tomcat 总体架构设计

在开始这篇文章的时候,忽然发现上一篇内容的题目不是很合适,不应该叫启动流程,更确切的应该是叫启动脚本。

在最开始,先介绍下 Tomcat 的总体设计,先有一个大概的印象,对 Tomcat 不至于那么陌生。

先介绍下 Tomcat 的一些基础组件(以下内容来自刘光瑞老师的「tomcat 架构解析」):

组件名称 介绍
Server 这个其实就是 Servlet 容器,一个 Tomcat 中只能有一个 Server
Service Service 表示一个或多个 Connector 的集合,这些 Connector 共享同一个 Container 来处理其请求。在同一个 Tomcat 实例内可以包含任意多个 Service 实例,它们彼此独立
Connector 这个是 Tomcat 的连接器,用于监听并转化 Servlet 的请求,同时将读取的 Socket 请求转交由 Container 处理,并且支持不同的协议以及不同的 I/O 方式。
Container 表示能够执行客户端请求并返回响应的一类对象。在 Tomcat 中存在不同级别的容器 Engine, Host, Context, Wrapper
Engine 表示整个 Servlet 引擎。在 Tomcat 中, Engine 为最高层级的容器对象。尽管 Engine 不是直接处理请求的容器,却是获取目标容器的入口。
Host Host 作为一类容器,表示 Servlet 引擎(即 Engine 中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册
Context Context 作为一类容器,用于表示 Servletcontext ,在 Servlet 规范中,一个 Servletcontext 即表示一个独立的 Web 应用。
Wapper Wapper 作为一类容器,用于表示 Web 应用中定义的 Servlet。
Executor 表示 Tomcat 组件间可以共享的线程池。

这个表格大致看一下,了解下 Tomcat 的一些组件,无需记忆,先有个印象,后面的内容会慢慢的聊到每一个组件。

作为一款 Web 容器, Tomcat 大体上实现了两个核心功能:

  • 处理 Socket 连接,负责网络字节流与 RequestResponse 对象的转化。
  • 加载并管理 Servlet ,以及处理具体的 Request 请求。

所以 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)。连接器负责对外交流,容器负责内部处理。

Tomcat 为了实现多种支持多种 I/O 模型和应用层协议,将 连接器(Connector)容器(Container) 进行了分离,

Tomcat 第三篇:总体架构设计

在 Tomcat 中,总会有一个 Service ,一个 Service 包含着多个 Connector 和一个 Container(或者说是它的顶层容器 Engine ) 。

而一个 Container 可以包含多个 Host ,一个 Host 内部又可以有多个 Context ,一个 Context 内部又会有多个 Servlet 。

Tomcat 的设计感觉上和 「俄罗斯套娃」 很像,一层包着一层。

Tomcat 启动初始化流程

先放一张启动流程图,然后我们再从源码的角度来验证这个启动流程。

Tomcat 第三篇:总体架构设计

从上面这张图可以清晰的了解到 Tomcat 的初始化的核心过程如下:

BootStrap -> Catalina -> Server -> Service -> Excutor -> Container (Engine -> Host -> Context -> Container)

1 BootStrap.main()

首先,整个程序是从 BootStrap 开始启动的,执行的是 BootStrap.main() :

public static void main(String args[]) {

    synchronized (daemonLock) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                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);
    }
}

在最开始,执行的是一段 synchronized 的同步代码,这里执行代码 bootstrap.init() 初始化了 bootstrap 对象,具体做了什么跟进去看下:

public void init() throws Exception {
    // 初始化类加载器
    initClassLoaders();
    // 设置线程类加载器,将容器的加载器传入
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    // 加载安全类
    SecurityClassLoad.securityClassLoad(catalinaLoader);
    // 初始化日志
    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    // 注意这里,通过反射加载了 Catalina
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    // 调用了上面加载的 Catalina 中的 setParentClassLoader 方法
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    // 将类加载器赋值成一个参数
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    // 看名字意思是一个共享加载器
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    // 调用了 Catalina 内的 setParentClassLoader 方法对 Catalina 类内的类加载器赋值
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}

注释都加好了,就不多解释了,我们接着回到之前的 main ,接下来还有一句是 daemon = bootstrap ,就是把我们刚才初始化过的 bootstrap 赋值给了 daemon 这个变量。

接下来是一大段的判断,主要是用来判断输入参数,这里面我们关注的就中间那一小段,当输入参数为 start 的时候,总共做了两件大事:

  • daemon.load(args)
  • daemon.start()

先跟进去看下 daemon.load(args) 都干了点啥:

private void load(String[] arguments) throws Exception {
    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    // 这里的 catalinaDaemon 刚才在 init() 方法里面进行了初始化,所以这里调用的实际上是 Catalina 的 load 方法
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled()) {
        log.debug("Calling startup class " + method);
    }
    method.invoke(catalinaDaemon, param);
}

这里实际上是开启了整个链式调用,接着往下追,看下 Catalina 里面的 load 方法。

在这里,会有一个方法的重载 load(String args[])load() ,不过关系不大,最终都是会调用到那个无参的方法上的。

public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // Create and execute our Digester
    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);
            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
            }
        }
    }

    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // 前面都是在初始化加载各种配置文件,使用了 Digester
    // Stream redirection
    initStreams();

    // Start the new server
    try {
        // 开始调用的 Server 的初始化方法
        // Server 是一个接口,并且继承了 Lifecycle ,进行生命周期的管理
        getServer().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");
    }
}

这一大坨大多数都是在加载各种配置文件,在这里,会完成对 server.xml 文件的解析。

我们关心的其实就只有最后那段 try...catch 里面的那一句 getServer().init() ,开始调用 Server 的初始化方法,这个方法就是开启一系列容器组件的加载方法。

不过看到最后一句的 log 打印,忽然有印象了,之前在启动 Tomcat 的时候,在日志的最后部分,都会看到这句打印,而且可以看到的是,这里并不是我之前以为的是使用毫秒数算出来的,而是取的纳秒数通过除 1000000 计算得出的,难道是这样算的更准?

getServer().init() 实际上是调用了 org.apache.catalina.Server 中的 init() 方法,而 org.apache.catalina.Server 则是一个接口,还继承了 org.apache.catalina.Lifecycle 进行容器生命周期的管理。

而抽象类 org.apache.catalina.util.LifecycleBase 则是实现了 org.apache.catalina.Lifecycle 接口,我们在 org.apache.catalina.Lifecycle 中打开 init() 方法:

public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}

这里我们的关注点是 initInternal() 这个方法,从名字上来看就是一个初始化方法,点过去一看,结果是一个抽象方法,实现类如下:

Tomcat 第三篇:总体架构设计

我们到 org.apache.catalina.core.StandardServer 中去看下其中的 initInternal() 方法,在这个方法的最后,看到了循环 Service 并且调用了 service.init()

for (Service service : services) {
    service.init();
}

因为一个 Server 是可以有多个 Service 的,所以这里用了一个循环,接下来就是一个顺序的容器初始化的调用过程:

StandardServer -> StandardService -> StandardEngine -> Connector

每个容器都在初始化自身相关设置的同时,将子容器初始化。

Tomcat 在启动初始化的时候,是通过链条式的调用,每初始化一个完成一个组件,就会在组件内调用下一个组件的初始化方法。

同样的操作在启动的时候也是一样的,不知道各位是否还记得我们在 Bootstrap.main() 中还有另一句代码 daemon.start()

public void start() throws Exception {
    if (catalinaDaemon == null) {
        init();
    }

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}

操作嘛都是一样的操作,从这段代码开始,调用了 Catalina.start() 的方法,然后开启了另一个链式调用。

我就不多说了,留给各位读者自己去翻翻看源码吧。

参考

https://segmentfault.com/a/1190000023475177

https://zhuanlan.zhihu.com/p/75723328

转载声明:本博客由极客挖掘机创作,采用 CC BY 3.0 CN 许可协议。可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

QR code