“万物本源”之Tomcat启动源码分析(前传)

这次,想到写这个系列,完全算是一次意外:本来我是想买本书,好好研究下SpringMVC的源码的,然后挑了本豆瓣评分超了8分的一本国人自己写的源码书,入了手。书中讲的的确很好,很符合我一直以来,对网络开发的一个底层思路:最原始使用Socket简单的传输数据--->根据数据的格式和协议进行字符串切割—>将切割好的进行协议封装—>将封装好的转给应用层的协议—>应用层根据协议使用自己的框架进行封装--->业务开发人员使用框架暴露出来的友好接口进行业务开发。我一直觉得我们日常的开发都陷入了整个环境的最后一环节,而很少有人愿意去拨开迷雾,了解整个这么多每个环节的细节。这也是一种技术的追求吧。即使这些个环境都了解了,也还是没有到最底层,我还碰到过一个问题呢:计算机网口接到数据,如何到达我们应用显示在屏幕上面的呢?让我们一步步来,技术人生,生为技术!

一、“空中俯瞰”

CRUD BOY的欢乐日常,乃创建一个Controller,然后在里面使用GetMapping或者PostMapping注解,写一个getXxxx或者saveXxxx,进一步写一个deleteXxx,修修sql脚本错误,修修NPE,改改VO,加加PO,参与讨论讨论业务逻辑,看看数据库数据……是否考虑过一个根本的问题:我们为什么浏览器访问一个127.0.0.1:8080/user/getUsers,就能执行我们的带GetMapping注解的方法呢?而进一步的问题:为什么我们机器能够解析一个互联网的网址请求呢?一切的应用层面的本源,就在于JavaEE与Tomcat。

1、Java被我们忽略的日常“触及”

JavaEE,是一个我们既陌生又熟悉的东西。平时我们整天提及,可是真正是怎么个东西,怎么被使用的,我们并不知道。简单通俗的说,JavaEE是一套标准,并不涉及具体的实现,都是各种各样的接口,就是想一统面向企业开发的天下。这里的“企业”并非我们世俗理解上面的企业,sun公司内部,可能更想表达是“有一定规模”的应用,而不仅仅局限于使用一个List、Map,操作一个字符串,写一个if判断逻辑等。那这种标准其中包括什么呢?我们简单列举几个:

  • Applet - Java Applet,一种网页运行的界面,后来被flash取代
  • EJB - 企业级JavaBean,就是管理我们创建的一个对象,不过加入了生命周期等
  • JAXP - Java XML解析API(Java API for XML Processing)
  • JDBC - Java数据库联接(Java Database Connectivity)
  • JMS - Java消息服务(Java Message Service)
  • JMX - Java Management,暴露一个对象的接口与属性给第三方终端,运行中修改参数
  • JNDI - Java名称与目录接口,使用一个简短的名称来定义一个服务,获取服务
  • JSP - Java服务器页面(Java Server Pages)
  • JTA - Java事务API(Java Transaction API)
  • Servlet - Java Servlet API,响应网络请求的接口

看到了吧,就是这些。然后第三方的厂商,根据自己的产品,来实现这些接口,提供给用户。这样用户就不用分别理解不同公司的不同协议版本,而都只用理解这JavaEE这一套就可以了。其中,类似于Tomcat这种,是一个容器,实现了对ServletJavaServer PageJSP)的支持,并提供了作为Web服务器的一些特有功能。相关的产品还有:

  • JBoss:实现了比较多的JavaEE标准
  • Jetty:类似于Tomcat
  • GlassFish:sun公司自己的服务器,能不全实现么?
  • Oracle WebLogic:比较稳定的 一个JavaEE服务器

其中类似于Tomcat和Jetty这种,只能算是一个轻量级的,大部分JavaEE标准并没有实现,主要就是为了实现一个Servlet,然后创建一个Socket底层连接,解析请求,并分配给一个Servlet来处理,并将处理之后的数据返回给用户。大概就是这么个东西。至于其他的JBoss、GlassFish这种,实现了完善的JavaEE标准,很重很重,我们这里只是想探究网络连接是怎么被接收处理的,这里不对这些重量级家伙进行讨论。

2、Tomcat之“痛”

这次大体看了Tomcat源码,仅仅是启动的源码,已经让我有一种感受:还是太年轻。单单启动过程,涉及的细节之多,我已经没办法描述了!仅仅理解一个加载机制,我觉得就已经要花费很多很多经历了,更别提还有xml解析用到的Digester的细节,JMX对象管理的细节,Java用于安全的管理机制,线程池自定义的细节,网络连接NIO的使用等等,到我写文章之日,我本人只是对容器做了全面的启动源码分析,对于网络请求这一块,还有待深入。下面,首先我列出来,源码中,针对于容器启动,非常关键性的一些类:

  • org.apache.catalina.startup.Bootstrap:main方法入口类
  • org.apache.catalina.startup.Catalina:解析server.xml配置类
  • org.apache.catalina.core.StandardServer:基础服务容器,就一个
  • org.apache.catalina.core.StandardService:基础服务对象,一个Server可对应多个Service
  • org.apache.catalina.core.StandardEngine:一个引擎类,一个Service对应1个Host
    • org.apache.catalina.core.StandardEngine:声明周期回调对象,这个没啥用
  • org.apache.catalina.core.StandardHost:域名服务对象,一个Engine可以对应多个Host
    • org.apache.catalina.startup.HostConfig:用于对设定host的路径下面的各个应用进行部署(拆包等)
  • org.apache.catalina.core.StandardContext:容器上下文,对应每个webapps下面的各个目录中的应用
    • org.apache.catalina.startup.ContextConfig:解析web.xml主要对象
  • org.apache.catalina.core.StandardWrapper:生成一个Servlet,一个Context包含一个Servlet
  • org.apache.catalina.core.StandardPipeline:管道对象,用于承载请求
  • org.apache.catalina.valves.ValveBase:每个管道中的最后一个节点
  • org.apache.catalina.util.LifecycleMBeanBase:Mbean管理父类,用于JMX的server注册
  • org.apache.catalina.util.LifecycleBase:声明周期基类,对声明周期进行管理

3、 基于容器整体图例

上面类的罗列感觉比较生涩,没办法看到Tomcat中这些个对象是怎么个包含关系,下面我画了个图,有助于理解:

有一些点稍作说明:

  • 一个Tomcat服务器,只能有一个Server对象的
  • 一个Tomcat服务器,可以有多个Service对象,每个Service服务不同的容器,并且使用不同的连接Connector
  • 重点的启动过程集中在两个函数:init()与start()
  • 全部的解析xml都使用Apache开源工具:Digester。我本次不做介绍,有兴趣移步深究
  • 上图中每个对象,都使用了JMX进行了管理,这东西也是一大块,有兴趣可以深究

下面是StandardServer与StandardService的类结构:

  • Lifecycle与LifecycleBase是Tomcat用来实现生命周期的主要方式,下面会讲
  • LifecycleMBeanBase与MBeanRegistration是用于JMX注册使用的

4、下面我们看看最内部的结构图例

最为核心的,是作为“容器”的存在。这里的容器,并不是一个简单的对象,而是一堆对象的封装,用于分别对于不同层次,不同数据的解析与加载,最关键的是下面四个,都继承了Container接口,也都加入了声明周期:

  • Engine:用于对子容器使用的各个组件进行初始化的作用,例如线程池
  • Host:主要用来部署应用的作用,例如解析war包,解压war包
  • Context:主要用来解析每个应用的web.xml并初始化应用类加载器,并加载应用类对象
  • Wrapper:初始化Servlet

我简单介绍了几个核心容器组件的作用,下面是图例:

  • 不难看出,一个Engine可以用多个Host,而一个Host也可以有多个Context(应用)
  • StandardPipeline是用于接收请求的一个对象,使用责任链设计模式,里面的Valve是链条的每个节点
  • 除了上面的图中的对象,还有2个很重要的监听对象,用于不同的容器里面:
    • org.apache.catalina.startup.HostConfig:部署WAR包
    • org.apache.catalina.startup.ContextConfig:解析web.xml

同样的,我们给出上面图例中涉及到的类对象的类图:

二、最上层的“领导”

下面我们开始解析源码,不求面面俱到,因为代码的细节非常之多,如果行行到位,我觉得可能短短几篇文章根本说不完。我们这里,主要把握一下整体的启动流程。首先从两个上层对象,也是入口对象入手:Bootstrap与Catalina。main方法存在于Bootstrap类里面,这个类有几个类属性与静态代码块,所以根据类的初始化机制,这些会首先被运行,我们先来看看这些:

/**
     * Daemon object used by main.
     */
    private static final Object daemonLock = new Object();
    private static volatile Bootstrap daemon = null;

    // 工作目录:存放conf、logs、temp、webapps和work的父目录,对于多个Tomcat实例的时候有用
    private static final File catalinaBaseFile;
    // 安装目录:存放bin和lib的父目录,每个Tomcat实例共享的目录
    private static final File catalinaHomeFile;

    private static final Pattern PATH_PATTERN = Pattern.compile("(\".*?\")|(([^,])*)");

    // 这段代码,主要用于基础配置文件路径的确认,先于main方法执行
    static {
        // Will always be non-null
        String userDir = System.getProperty("user.dir");

        // Home first
        String home = System.getProperty(Globals.CATALINA_HOME_PROP);
        File homeFile = null;

        if (home != null) {
            File f = new File(home);
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        if (homeFile == null) {
            // 如果不设置启动参数的话,会使用bootstrap.jar文件的父级目录当做安装目录
            File bootstrapJar = new File(userDir, "bootstrap.jar");

            if (bootstrapJar.exists()) {
                File f = new File(userDir, "..");
                try {
                    homeFile = f.getCanonicalFile();
                } catch (IOException ioe) {
                    homeFile = f.getAbsoluteFile();
                }
            }
        }

        if (homeFile == null) {
            // 如果没有bootstrap.jar,就使用当前程序运行的目录作为安装目录
            File f = new File(userDir);
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        catalinaHomeFile = homeFile;
        System.setProperty(
                Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());

        // 确定多Tomcat实例情况下的程序运行目录,单实例Tomcat这个目录和安装目录是同一个
        String base = System.getProperty(Globals.CATALINA_BASE_PROP);
        if (base == null) {
            catalinaBaseFile = catalinaHomeFile;
        } else {
            File baseFile = new File(base);
            try {
                baseFile = baseFile.getCanonicalFile();
            } catch (IOException ioe) {
                baseFile = baseFile.getAbsoluteFile();
            }
            catalinaBaseFile = baseFile;
        }
        System.setProperty(
                Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
    }

很简单,就是基础的文件操作,用来确定两大基本目录的位置。然后,下面就看看我们喜闻乐见的main方法:

public static void main(String args[]) {
        synchronized (daemonLock) {
            // 这里的daemon就是main方法所在的对象:Bootstrap
            if (daemon == null) {
                Bootstrap bootstrap = new Bootstrap();
                try {
                    // 关键:这里主要进行了类加载器的构建与关键路径的保存
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                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) {
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

1、类加载器的初始化

这个过程相对后面的容器初始化来说比较简单,涉及一个配置文件:catalina.xml。主要就在Bootstrap对象里面完成的,下面就是其init方法:

public void init() throws Exception {

        // 初始化三大加载器
        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        if (log.isDebugEnabled()) {
            log.debug("Loading startup class");
        }
        // 我们使用最上层的加载器加载catalina对象,
    	// 这个加载器和SharedLoader加载器是兄弟关系,父亲都是commonLoader
        Class<?> startupClass =
            catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

        // 这里是给Catalina对象设置父加载器,使用反射方法
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        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);
        method.invoke(startupInstance, paramValues);
        // 保存我们的Catalina服务器对象
        catalinaDaemon = startupInstance;

    }

下面是initClassLoaders方法:

private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                /**
                 * 如果catalina.properties文件中没有配置common加载器,
                 * 将使用本类的加载器,其实就是AppClassLoader 
                 */
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

发现其主要的加载方法是createClassLoader:

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {

        // 这里涉及到一个配置文件catalina.properties
        String value = CatalinaProperties.getProperty(name + ".loader");
	    // 如果配置文件中没有配置,将返回父parent
        if ((value == null) || (value.equals("")))
            return parent;

        value = replace(value);

        List<Repository> repositories = new ArrayList<>();

        // 将catalina.properties配置文件中的配置转换成真实路径
        String[] repositoryPaths = getPaths(value);

        for (String repository : repositoryPaths) {
            // 用于探测是不是通过具体的网络加载的路径
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            // 将配置文件中的路径值进行一定的封装
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(new Repository(repository, RepositoryType.DIR));
            }
        }
        /**
         * 这里主要通过配置文件中配置的几个文件路径,其中包括
         * ${catalina.base}/lib
         * ${catalina.base}/lib/*.jar
         * ${catalina.home}/lib
         * ${catalina.home}/lib/*.jar
         *
         * 然后以这些路径为根基,生成ClassLoader,主要是一个URLClassLoader
         *
         */
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

下面是catalina.properties里面针对这几个类加载器的默认配置:

common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

所以根据源码,很容易得出,如果默认情况下的话,那catalinaLoader与sharedLoader其实都是commonLoader,三个是同一个对象同一个加载器。下面是Tomcat的加载器示例图,具体对于加载器的分析,我单独以后拿一篇来讲,这里先给出来:

2、核心反射调用

下面就涉及了两个核心调用:

daemon.load(args);
daemon.start();

别看只是调用本类(Bootstrap)的load和start方法,其内部可是反射调用了StandardServer的这两个方法,而Server中又分别调用了StandardService的这两个方法,然后Service又分别调用了StandardEngine的这两个方法。如此一来整体容器就被初试化了,所以整体启动过程,其实就是疯狂的调用各个对象的load和start方法!首先我们来看看最初Bootstrap类里面的这两个方法:

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;
    }
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled())
        log.debug("Calling startup class " + method);
    method.invoke(catalinaDaemon, param);
}


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

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

}

会发现都非常的简单,就是调用Catalina对象里面响应名称的方法,我这里就不注释了。下面我们就陷入核心类与容器,一探究竟。

三、“最美丽的海岛”

Catalina(好吧,写标题的时候查了下,这是一个岛的名称),在load被调用之后,随后调用了start方法,start方法的最后是一个阻塞操作等待结束的命令,所以main方法所在的线程至此的以保存,这就形成了一个整体的容器,等待着新的请求过来。

1、下面我们先来看看load方法

public void load() {

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

    long t1 = System.nanoTime();

    initDirs();

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

    // 关键:主要用于解析server.xml文件里面的具体元素
    Digester digester = createStartDigester();



    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;

    // 下面主要用于定位server.xml这个文件,创建这个文件的输入流对象
    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);
                }
            }
        }

        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);
                }
            }
        }

        // 如果找不到,返回加载,不能进行load操作,所以后面的start应该不会成功的
        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");
                }
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);
            // 这个操作其实是设置digester解析时候最父级的对象,这里就设成了Catalina这个对象
            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());

    initStreams();

    // 开始调用StandardServer的init方法
    try {
        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对象是怎么被初始化的,根源在于使用了一个叫做Digester的xml解析器,然后解析了server.xml文件,下面是代码片段:

/**
 * Digester:是一个底层使用SAX解析xml文件的Apache工具,也是一大块。
 * 整个tomcat启动过程中对各个配置文件的解析,都是使用这个工具的。
 */
protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);
    Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    List<String> attrs = new ArrayList<>();
    attrs.add("className");
    fakeAttributes.put(Object.class, attrs);
    digester.setFakeAttributes(fakeAttributes);
    digester.setUseContextClassLoader(true);

    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    // 这里调用父级的setServer方法,其实就是Catalina对象里面的setServer方法
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");

    digester.addObjectCreate("Server/GlobalNamingResources",
                             "org.apache.catalina.deploy.NamingResourcesImpl");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResourcesImpl");

    digester.addObjectCreate("Server/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");

    digester.addObjectCreate("Server/Service/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Service/Listener");
    digester.addSetNext("Server/Service/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    //Executor
    digester.addObjectCreate("Server/Service/Executor",
                             "org.apache.catalina.core.StandardThreadExecutor",
                             "className");
    digester.addSetProperties("Server/Service/Executor");

    digester.addSetNext("Server/Service/Executor",
                        "addExecutor",
                        "org.apache.catalina.Executor");


    digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());
    digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
        new String[]{"executor", "sslImplementationName", "protocol"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");

    ..................

        // Add RuleSets for nested elements
        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
    digester.addRuleSet(new EngineRuleSet("Server/Service/"));
    digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
    digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
    addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
    digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

    // When the 'engine' is found, set the parentClassLoader.
    digester.addRule("Server/Service/Engine",
                     new SetParentClassLoaderRule(parentClassLoader));
    addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");

    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return digester;

}

这里类似于Server或者Server/GlobalNamingResources对应的都是xml文件中的标签和子标签,addSetNext这个方法是调用父标签创建的对象中的一个指定方法请看第一个地方:

digester.addObjectCreate("Server",
                         "org.apache.catalina.core.StandardServer",
                         "className");
digester.addSetProperties("Server");
// 这里调用父级的setServer方法,其实就是Catalina对象里面的setServer方法
digester.addSetNext("Server",
                    "setServer",
                    "org.apache.catalina.Server");

而在上面load方法的后面有这么一段:

inputSource.setByteStream(inputStream);
// 这个操作其实是设置digester解析时候最父级的对象,这里就设成了Catalina这个对象
digester.push(this);
digester.parse(inputSource);

结合上面两端源码,其实就是根据server.xml文件中的Server标签,创建一个StandardServer对象,然后传入Catalina对象中的setServer方法,这个方法就是设置Catalina对象属性server的,这样一来,server就被初始化了

番外:这里我之所以要解说一下,主要因为通篇的tomcat源码,很多地方都是通过这种方式初始化对象的。因为一个Tomcat容器涉及到好几个配置文件,每次对配置文件解析之后,直接用,比较方便,后面我就不再详细描述原理,具体的Digester的使用也不是我在这里要讨论的。

2、下面我们来看看start方法

public void start() {
    // 如果server为空的时候,先调用load方法进行初始化操作
    if (getServer() == null) {
        load();
    }
    // 对非正常情况进行保护
    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }
    long t1 = System.nanoTime();

    // 这里就调用内层Server的start方法了
    try {
        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()) {
        // 我们控制台喜闻乐见的server startup日志输出是这里打印的!
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    /**
         * 这里主要向虚拟机注册关闭的回调对象
         * 内部是一个线程,主要调用了Catalina的stop方法,用来终结操作
          */ 
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                false);
        }
    }

    if (await) {
        /*
         * 这个wait的内层调用了server的await方法
         * 内层其实是起了个关闭的ServerSocket进行accept监听
         * 然后解析是否是关闭请求
         * 
          */
        await();
        stop();
    }
}

自我感觉start的方法比较简单,主要用于调用内层Server,然后就等待了。

四、非常工整的设计

在深入看StandardServer源码之前,我们要先看一个Tomcat设计的非常好的体系:生命周期。源码看下啦,会发现非常的工整,不啰嗦,不生涩,同时也恰到好处,我觉得这就是值得我们学习的代码和思想!生命周期,顾名思义,主要就是在整个Tomcat的初始化、启动、关闭等相关节点,进行监听,可以动态加入自定义的监听器,以达到我们的业务诉求。整个Tomcat的生命周期,设置了一个最基本的接口类:Lifecycle,而他又有个抽象的实现类:LifecycleBase。我们所涉及到的容器所有相关的具体实现类,全部都继承了这个抽象类LifecycleBase!下面我们单独来看看Lifecycle的接口设计:

public interface Lifecycle {


    public static final String BEFORE_INIT_EVENT = "before_init";


    public static final String AFTER_INIT_EVENT = "after_init";


    public static final String START_EVENT = "start";


    public static final String BEFORE_START_EVENT = "before_start";


    public static final String AFTER_START_EVENT = "after_start";


    public static final String STOP_EVENT = "stop";


    public static final String BEFORE_STOP_EVENT = "before_stop";


    public static final String AFTER_STOP_EVENT = "after_stop";


    public static final String AFTER_DESTROY_EVENT = "after_destroy";


    public static final String BEFORE_DESTROY_EVENT = "before_destroy";


    public static final String PERIODIC_EVENT = "periodic";


    public static final String CONFIGURE_START_EVENT = "configure_start";


    public static final String CONFIGURE_STOP_EVENT = "configure_stop";



    public void addLifecycleListener(LifecycleListener listener);


    public LifecycleListener[] findLifecycleListeners();


    public void removeLifecycleListener(LifecycleListener listener);


    public void init() throws LifecycleException;

    public void start() throws LifecycleException;


    public void stop() throws LifecycleException;

    public void destroy() throws LifecycleException;


    public LifecycleState getState();


    public String getStateName();


    public interface SingleUse {
    }
}

会发现,定义了13个常量,几个常量对应着容器里面对应的生命周期名称,其它的抽象方法,其实都能顾名思义,分别是容器在各个周期时候调用的方法。下面这个枚举则是对生命周期的字符串进行了进一步的封装,便于后面使用:

public enum LifecycleState {
    NEW(false, null),
    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
    STARTING(true, Lifecycle.START_EVENT),
    STARTED(true, Lifecycle.AFTER_START_EVENT),
    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
    STOPPING(false, Lifecycle.STOP_EVENT),
    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
    FAILED(false, null);

    // 标志位,在具体运行过程中,进行分支判断
    private final boolean available;
    // 可见,生命周期的名称,其实就是我们所需要的触发事件名称
    private final String lifecycleEvent;

    private LifecycleState(boolean available, String lifecycleEvent) {
        this.available = available;
        this.lifecycleEvent = lifecycleEvent;
    }
    public boolean isAvailable() {
        return available;
    }

    public String getLifecycleEvent() {
        return lifecycleEvent;
    }
}

而LifecycleBase对这个接口进行了初步的实现,在LifecycleBase也体现着“生命周期”这四个字的含义。他会对具体的例如init方法进行包装,使用模板设计模式思想,对子类的具体实现方法进行了定制。而具体上层调用的生命周期方法的时候,其实内部是调用定制的方法,然后在定制方法的前后,分别插入回调监听器和状态扭转的代码。很值得学习的代码!下面我就用init这个方法,做一个说明展示:

public abstract class LifecycleBase implements Lifecycle{
	@Override
    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            // init方法必须要当前是最开始对象创建之后就被调用
            // 否则就会抛异常
            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());
        }
    }
    /**
     * 这就是定制方法,子类就是实现这个方法,而不用实现init
     */
    protected abstract void initInternal() throws LifecycleException;
}

下面是setStateInternal方法,让我们一探到底:

private synchronized void setStateInternal(LifecycleState state, Object data
		, boolean check) throws LifecycleException {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("lifecycleBase.setState", this, state));
    }
    // 这里主要设置是否要进行状态翻转之前的校验
    if (check) {
        // 状态字段永远不允许被设置成null,否则抛错
        if (state == null) {
            invalidTransition("null");
            return;
        }

        // 这里要进行一系列特殊扭转的判断:
        // 1:任何方法都可以扭转成failed
        // 2:startInternal() 允许 STARTING_PREP 状态扭转成 STARTING
        // 3:stopInternal() 允许 STOPPING_PREP 扭转成 STOPPING 且 FAILED 扭转成 STOPPING
        if (!(state == LifecycleState.FAILED ||
              (this.state == LifecycleState.STARTING_PREP &&
               state == LifecycleState.STARTING) ||
              (this.state == LifecycleState.STOPPING_PREP &&
               state == LifecycleState.STOPPING) ||
              (this.state == LifecycleState.FAILED &&
               state == LifecycleState.STOPPING))) {
            // No other transition permitted
            invalidTransition(state.name());
        }
    }
    // 更改当前状态
    this.state = state;
    // 注意NEW的事件名称是不可能有监听器回调的,因为这个名称是一个null
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {
        // 在这里触发监听器
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

protected void fireLifecycleEvent(String type, Object data) {
	LifecycleEvent event = new LifecycleEvent(this, type, data);
    // 如果有对应的监听器对象被add进来,会逐个调用里面的lifecycleEvent实现方法的
    for (LifecycleListener listener : lifecycleListeners) {
    	listener.lifecycleEvent(event);
    }
}

到此,整个生命周期的设计,原理已经讲清楚了。这一块重在理念非常好,并非很难理解。在其后的各个地方,都会按照上面的init这个例子,进行类似的操作。下面如果关键,我还会列出具体生命周期源码的。

五、终于等到你

接下来我们终于陷入到更下一层:Server。其实现类是StandardServer。在具体看init和start函数之前,我们先来弄清楚一个点:server是怎么创建出来的?(这个很关键,要知道,创建的时候有可能加入很多监听器啊之类的东西,后面很多容器对象都是使用这种方式进行的创建)很简单,就是前面使用Digester解析server.xml的时候进行创建的,我们来看看前面在Catalina里面有列出来的代码片段:

// 这里就是创建Server
digester.addObjectCreate("Server",
                        "org.apache.catalina.core.StandardServer",
                        "className");
digester.addSetProperties("Server");
// 这里调用父级的setServer方法,其实就是Catalina对象里面的setServer方法
digester.addSetNext("Server",
                    "setServer",
                    "org.apache.catalina.Server");
digester.addObjectCreate("Server/Listener",
                         null, // 这个地方为null的时候,要由xml中指定
                         "className");
digester.addSetProperties("Server/Listener");
/**
* 这里调用父级的addLifecycleListener方法,
* 其实就是调用StandardServer里面的addLifecycleListener,
* 其实就是调用前面介绍的生命周期中LifecycleBase中实现的addLifecycleListener
*/
digester.addSetNext("Server/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

接下来我们列出片段的serve.xml,这样就能和上面的解析代码对应起来:

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
............
</Server>

可以看到,其实在StandardServer初始化的时候,一共有这么多个监听器,具体的类名,都是用标签上面的className进行了标注。

1、Server的init方法

在上一章我们介绍了整个容器的生命周期的设计,所以这里调用init,其实就是调用的LifecycleBase里面的init,而StandardServer对象里面并没有实现init方法。而对于LifecycleBase里面的成员属性,我们已创建就被赋了一些初始值,下面我们来看看,也是比较重要的点:

public abstract class LifecycleBase implements Lifecycle {

    private static final Log log = LogFactory.getLog(LifecycleBase.class);

    private static final StringManager sm = StringManager.getManager(LifecycleBase.class);


    /**
     * 看到了吧,这就是存储监听器的List容器
     */
    private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();


    /**
     * 这个就是每个容器一旦被初始化,
     他的生命周期就是NEW,名称是一个null值
     */
    private volatile LifecycleState state = LifecycleState.NEW;


    private boolean throwOnFailure = true;
    
    ..............
}

具体容器的init逻辑,都被放到了StandardServer里面,实现的定制方法initInternal:

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 这里一系列的操作是JMX相关的注册
    onameStringCache = register(new StringCache(), "type=StringCache");

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

    // 这里是对命名服务进行JMX相关的注册
    globalNamingResources.init();

    /**
         * 这里主要是把父级的类加载器所携带的,
         * 运行和安装目录下lib文件夹中所有Jar包中,
         * 是否有MANIFEST文件,加入到一个扩展校验器中
         */
    if (getCatalina() != null) {
        // 这个ClassLoader其实就是前面的SharedClassLoader,
        // 因为没有配置,所以就是CommonClassLoader
        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 e) {
                            // Ignore
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }
    // 这里加调用再下一层的容器了:StandardService
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}

会发现,server的init内部,实际上面很简单,JMX的注册和校验器是另外的大话题我们先不讲,之后就是调用下一层的service中的init方法了。

2、Server的start方法

同样的,start方法其实还是LifecycleBase里面实现的:

public final synchronized void start() throws LifecycleException {
    if (LifecycleState.STARTING_PREP.equals(state) || 
        LifecycleState.STARTING.equals(state) ||
        LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }
        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        // 没进行 初始化会先调用init
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        // 失败的情况,会调用stop进行停止
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
               !state.equals(LifecycleState.STOPPED)) {
        // 这里是异常情况
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }
    try {
        // 状态翻转
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        // 正常调用的子类定制方法
        startInternal();
        // 子类方法结束之后的状态判断与翻转
        if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

会发现,其实会对处在各种周期内的容器,进行相对应的处理(方法调用),保证了状态的确定性。下面是StandardServer里面实现的定制方法startInternal:

protected void startInternal() throws LifecycleException {
    // 这里会触发一个生命周期的回调:configure_start
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    // 这里直接翻转了当前容器所处的生命周期
    setState(LifecycleState.STARTING);
    // 调用JNDI的start方法
    globalNamingResources.start();

    // 分别调用service里面的start方法
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}

这里也比较简单,这里不再多做解释。

六、再进一步

下面我们来看看具体的StandardService里面做了什么

1、我们直接看initInternal

protected void initInternal() throws LifecycleException {
    // 调用LifecycleMBeanBase中的initInternal
    // 内部主要就是进行JMX的注册操作
    super.initInternal();

    // 这里调用再下一层的容器:StandardEngine
    if (engine != null) {
        engine.init();
    }

    // 这里对自己定义的线程池进行初始化操作(请看下面的server.xml代码片段)
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }
    mapperListener.init();

    // 连接器,重点!我会在下一篇的文章中重点讲
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}

对比性的,我会先列出来StandardService初始化时候的代码片段,是在Catalina的createStartDigester方法中,又是通过Digester对server.xml进行了解析(看到了吧,这东西贯穿始终)

digester.addObjectCreate("Server/Service",
                         "org.apache.catalina.core.StandardService",
                         "className");
digester.addSetProperties("Server/Service");
// 调用StandardServer里面的addService方法将StandardService加入到容器里面
digester.addSetNext("Server/Service",
                    "addService",
                    "org.apache.catalina.Service");

digester.addObjectCreate("Server/Service/Listener",
                         null, // MUST be specified in the element
                         "className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

// 线程池初始化
digester.addObjectCreate("Server/Service/Executor",
                         "org.apache.catalina.core.StandardThreadExecutor",
                         "className");
digester.addSetProperties("Server/Service/Executor");
// 调用StandardService里面的addExcutor方法,添加自定义的线程池
digester.addSetNext("Server/Service/Executor",
                    "addExecutor",
                    "org.apache.catalina.Executor");

// 对Connector的初始化
digester.addRule("Server/Service/Connector",
                 new ConnectorCreateRule());
digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
    new String[]{"executor", "sslImplementationName", "protocol"}));
digester.addSetNext("Server/Service/Connector",
                    "addConnector",
                    "org.apache.catalina.connector.Connector");

其实也是比较规整,一点点的解析->初始化->调用父级对象方法,重复这个动作!下面我们来看看server.xml的配置文件的代码片段:

<Server port="8005" shutdown="SHUTDOWN">
    ........
    <Service name="Catalina">

        <!--这里是可以自己定义一个线程池的,不定义使用默认的-->
        <!--
        <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
            maxThreads="150" minSpareThreads="4"/>
        -->


        <!-- 这里就是connector的定义,对应上面的代码-->
        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        
        <!-- 当然我们可以自己定义connector,然后使用自定义个线程池-->
        <!--
        <Connector executor="tomcatThreadPool"
                   port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        -->
        <!-- 下面是一系列的自定义的connector-->
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
                   maxThreads="150" SSLEnabled="true">
            <SSLHostConfig>
                <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->
        
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
                   maxThreads="150" SSLEnabled="true" >
            <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
            <SSLHostConfig>
                <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                             certificateFile="conf/localhost-rsa-cert.pem"
                             certificateChainFile="conf/localhost-rsa-chain.pem"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->
        <!-- 这是Apache自己的一个连接协议 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
        ............
    </Service>
    ............
</Server>

另外再说明一点:例如<Service name="Catalina">这种,在具体实现对象里面有对应的成员属性,来对应标签属性的,所以之后的都会使用这种模式进行从xml到类成员属性值的映射,是一个比较方便的方式。例如这个service,再StandardService里面可以找到:

public class StandardService extends LifecycleMBeanBase implements Service {

    private static final Log log = LogFactory.getLog(StandardService.class);


    // ----------------------------------------------------- Instance Variables

    /**
     * 看到了吧,这个对应了标签属性里面的name值
     */
    private String name = null;
    
    。。。。。。。。。
        
}

2、我们直接看startInternal

protected void startInternal() throws LifecycleException {
    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
	// 这里翻转了下状态
    setState(LifecycleState.STARTING);
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }
    mapperListener.start();
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

和initInternal很类似,代码方位都没怎么变,直接把init方法的调用,换成了分别调用各个的start方法。要单独介绍说明的地方是,状态翻转方法setState,内部调用了父类LifecycleBase的setState方法:

protected synchronized void setState(LifecycleState state) throws LifecycleException {
    setStateInternal(state, null, true);
}

直接调用了setStateInternal,这个方法也是上面我们介绍过的,这里我们再列出来:

private synchronized void setStateInternal(LifecycleState state
    , Object data, boolean check)
    throws LifecycleException {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("lifecycleBase.setState", this, state));
    }
    // 这里主要设置是否要进行状态翻转之前的校验
    if (check) {
        // 状态字段永远不允许被设置成null,否则抛错
        if (state == null) {
            invalidTransition("null");
            return;
        }

        // 这里要进行一系列特殊扭转的判断:
        // 1:任何方法都可以扭转成failed
        // 2:startInternal() 允许 STARTING_PREP 状态扭转成 STARTING
        // 3:stopInternal() 允许 STOPPING_PREP 扭转成 STOPPING 且 FAILED 扭转成 STOPPING
        if (!(state == LifecycleState.FAILED ||
              (this.state == LifecycleState.STARTING_PREP &&
               state == LifecycleState.STARTING) ||
              (this.state == LifecycleState.STOPPING_PREP &&
               state == LifecycleState.STOPPING) ||
              (this.state == LifecycleState.FAILED &&
               state == LifecycleState.STOPPING))) {
            // No other transition permitted
            invalidTransition(state.name());
        }
    }
    // 更改当前状态
    this.state = state;
    // 注意NEW的事件名称是不可能有监听器回调的,因为这个名称是一个null
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {
        // 在这里触发监听器
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

可以见得,再具体的容器实现类里面调用setState方法,是会直接触发监听器的,而不仅仅是状态翻转!

七、引擎

接下来的几个章节,就进入了重点了,真正的所谓的容器:Engine、Host、Context和Wrapper。这些是Tomcat服务器启动的核心!之所以叫容器,是因为他们都继承了一个公共的接口:Container。真正要做集群的话,也是从这几个容器开始的。一个请求过来之后,也是调用这几个容器里面的管道节点,然后一步步解析到Servlet里面的。下面我们先从Engine的实现类StandardEngin开始。

1、初始化

对于这几个容器开始,初始化有点特殊,都封装到一个特有的对象中,根据Digester的规则,进行解析。

// Catalina中的createStartDigester方法片段
protected Digester createStartDigester() {
    digester.addRuleSet(new EngineRuleSet("Server/Service/"));
    digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
    digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
    addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
}

我们看看EnginRuleSet对象中的:

public class EngineRuleSet implements RuleSet {
    // 这个代表着父标签的前缀,如:Server/Service/
    protected final String prefix;

    public EngineRuleSet() {
        this("");
    }


    public EngineRuleSet(String prefix) {
        this.prefix = prefix;
    }
    @Override
    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Engine",
                                 "org.apache.catalina.core.StandardEngine",
                                 "className");
        digester.addSetProperties(prefix + "Engine");
        /**
         * 这里添加了个重要的监听器:EngineConfig
         * 使用的是另外一个规则对象进行的封装:LifecycleListenerRule
         */
        digester.addRule(prefix + "Engine",
                         new LifecycleListenerRule(
                          "org.apache.catalina.startup.EngineConfig",
                          "engineConfigClass"));
        digester.addSetNext(prefix + "Engine",
                            "setContainer",
                            "org.apache.catalina.Engine");

        // 这里就是集群标签的解析,如果有的话
        digester.addObjectCreate(prefix + "Engine/Cluster",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Engine/Cluster");
        digester.addSetNext(prefix + "Engine/Cluster",
                            "setCluster",
                            "org.apache.catalina.Cluster");
        
        // 这里解析Engine标签的直属子标签中的监听器,其实就是Engine级别的监听器
        digester.addObjectCreate(prefix + "Engine/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Engine/Listener");
        digester.addSetNext(prefix + "Engine/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        // 这里解析的是权限规则
        digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));

        // 这个东西中文叫做"阀",就是管道中的结点,这里我们可以定义自己的结点
        digester.addObjectCreate(prefix + "Engine/Valve",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Engine/Valve");
        // 这里会将结点加入到当前 容器的管道中
        digester.addSetNext(prefix + "Engine/Valve",
                            "addValve",
                            "org.apache.catalina.Valve");
    }
}

对应的我们看看server.xml中对应解析的源头:

<Server port="8005" shutdown="SHUTDOWN">
  ........
  <Service name="Catalina">

    ........
     <!-- 这里就是Engine的标签 -->
    <Engine name="Catalina" defaultHost="localhost">

      ........
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      .........
    </Engine>
  </Service>
</Server>

就是解析这段xml的。接下来我看看上面的那个监听器是如何被加到容器里面的,这就要继续深入LifecycleListenerRule:

public class LifecycleListenerRule extends Rule {

    public LifecycleListenerRule(String listenerClass, String attributeName) {


        this.listenerClass = listenerClass;
        this.attributeName = attributeName;

    }

    // 这个对应监听器实现类的全路径
    private final String attributeName;


    // 这个对应监听器的属性名称,对应xml中的标签中的属性名称
    private final String listenerClass;

    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        // 获取当前容器
        Container c = (Container) digester.peek();
        Container p = null;
        // 获取父级容器
        Object obj = digester.peek(1);
        if (obj instanceof Container) {
            p = (Container) obj;
        }

        String className = null;

        // 检查当前标签的属性中配了这个名称了没
        if (attributeName != null) {
            String value = attributes.getValue(attributeName);
            if (value != null)
                className = value;
        }

        // 检查父级标签的属性中配了这个名称了没
        if (p != null && className == null) {
            String configClass =
                (String) IntrospectionUtils.getProperty(p, attributeName);
            if (configClass != null && configClass.length() > 0) {
                className = configClass;
            }
        }

        // 使用传入的类名称
        if (className == null) {
            className = listenerClass;
        }

        // 生成监听器名称
        Class<?> clazz = Class.forName(className);
        LifecycleListener listener = (LifecycleListener) 
            clazz.getConstructor().newInstance();

        // 将监听器加入到当前容器里面
        c.addLifecycleListener(listener);
    }

}

至此,监听器就在解析xml过程中加入到了容器里面

2、Engine的initInternal

真对这几个Container类型的容器,都有个默认的实现抽象类ContainerBase,这种类似于LifeCycle设计。下面是StandardEngine里面的initInternal方法:

protected void initInternal() throws LifecycleException {
    getRealm();
    // 非常简单,直接调用了ContainerBase里面的initInternal方法
    super.initInternal();
}

下面是ContainerBase里面的方法:

@Override
protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreadsInternal());
    super.initInternal();
}
private int getStartStopThreadsInternal() {
    // 这里默认为1
    int result = getStartStopThreads();

    if (result > 0) {
        return result;
    }

    // 如果result为0的情况下,返回CPU的核数
    result = Runtime.getRuntime().availableProcessors() + result;
    if (result < 1) {
        // 防止获取不到底层CPU核数的情况
        result = 1;
    }
    return result;
}
private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        // 第一次执行的时候,走这个分支
        if (!(startStopExecutor instanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService();
        }
    } else {
        if (startStopExecutor instanceof ThreadPoolExecutor) {
            ((ThreadPoolExecutor) startStopExecutor).setMaximumPoolSize(threads);
            ((ThreadPoolExecutor) startStopExecutor).setCorePoolSize(threads);
        } else {
            BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
            ThreadPoolExecutor tpe = new ThreadPoolExecutor(threads, threads, 10,
							TimeUnit.SECONDS,
                            startStopQueue,
                            new StartStopThreadFactory(getName() + "-startStop-"));
            tpe.allowCoreThreadTimeOut(true);
            startStopExecutor = tpe;
        }
    }
}

可以见得,其实Engine的init过程,核心创建了一个线程池

3、Engine的startInternal

protected synchronized void startInternal() throws LifecycleException {
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

    super.startInternal();
}

同样的,还是调用ContainerBase里面的方法,我们接下来去看真正的startInternal方法:

protected synchronized void startInternal() throws LifecycleException {
    logger = null;
    getLogger();
    // 看看是否是集群,启动集群
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }

    // 看看是否有加载权限管理的,启动
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // 启动子容器,异步,例如:对于Engine来说的子容器就是Host
    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])));
    }

    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();
    }
    // 这个方法注意:因为内部会进行监听器的调用,所以会执行EngineConfig的方法
    setState(LifecycleState.STARTING);
    // 这个是一个守护线程的启动,内部会判断是否session会超时等
    threadStart();
}

八、站点

这里我们再递进一层Host,实现类是StandardHost,对于Host来说,其初始化和Engine很类似,同样是解析server.xml文件中的对应标签,我们这里就不多说。到了Host这一层,会进行一个,我们平时常识性的一些操作:部署工程!例如我们将一个WAR包放到了webapps文件夹下面,为什么Tomcat启动之后就会变成一个文件夹呢。就是在Host这一层进行的。

1、简单的介绍initInternal和startInternal

对于Host来说实现类StandardHost没有实现定制方法initInternal,都是使用ContainerBase的:

protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreadsInternal());
    super.initInternal();
}

和上面一样,不解说了。

protected synchronized void startInternal() throws LifecycleException {
    // 这里是向Host的管道里面添加一个特殊的结点"阀":ErrorReportValve,后面解说
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                "standardHost.invalidErrorReportValveClass",
                errorValve), t);
        }
    }
    // 调用父类ContainerBase的方法,和上面一模一样的解说
    super.startInternal();
}

同样的,其实不多逻辑,下面的HostConfig是重点

2、项目部署

还记得上面的Engine章节介绍的ContainerBase中的startInternal方法吗?里面最后调用了setState方法,内部调用了监听器,这里的监听器就是HostConfig,再Host初始化的时候加入到生命周期里面的。我们就来看看HostConfig实现的生命收起方法lifecycleEvent方法:

@Override
public void lifecycleEvent(LifecycleEvent event) {
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            // 这里主要将Host里面的部署配置读取到当前HostConfig中来
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // 在不同的生命周期环境,分别进行不同的处理
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        // StandardHost的start方法最后就调用这个方法
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

我们主要解说的是启动,这里只跟进一下start方法:

public void start() {

    if (log.isDebugEnabled())
        log.debug(sm.getString("hostConfig.start"));

    try {
        // 这个地方主要进行JMX的注册
        ObjectName hostON = host.getObjectName();
        oname = new ObjectName
            (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
        Registry.getRegistry(null, null).registerComponent
            (this, oname, this.getClass().getName());
    } catch (Exception e) {
        log.error(sm.getString("hostConfig.jmx.register", oname), e);
    }

    // 如果webapps文件不是一个文件夹,那就不进行下面部署操作了,所以这里要把标志位置false
    if (!host.getAppBaseFile().isDirectory()) {
        log.error(sm.getString("hostConfig.appBase", host.getName(),
                               host.getAppBaseFile().getPath()));
        host.setDeployOnStartup(false);
        host.setAutoDeploy(false);
    }

    // 默认为true
    if (host.getDeployOnStartup())
        deployApps();

}

如果webapps存在且是一个文件夹,那就调用deployApps方法:

protected void deployApps() {

    // 这个就是webapps文件夹
    File appBase = host.getAppBaseFile();
    /**
     * 创建或者获取一个配置文件的临时文件夹,
     * 默认情况是运行目录下的conf/Catalina/localhost目录
     */
    File configBase = host.getConfigBaseFile();

    // 这里主要进行了应用的过滤,可配置一些应用不进行部署
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 这里是对上面的配置文件夹下面的xml文件进行部署
    deployDescriptors(configBase, configBase.list());
    // 这里主要是对war包进行拆包并部署,其实就是文件操作
    deployWARs(appBase, filteredAppPaths);
    // 这里是对不是war的的其他文件夹进行部署
    deployDirectories(appBase, filteredAppPaths);

}

这就是部署的核心源码,分别对应了三个deployXxxx,分别部署不同类型的文件。说明一下,这三个方法,都是并行且同步的,使用了Host初始化的时候,初始化的线程池startStopExecutor。具体我举例其中deployDescriptors方法,给大家看一下:

protected void deployDescriptors(File configBase, String[] files) {

    if (files == null)
        return;

    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();

    for (int i = 0; i < files.length; i++) {
        File contextXml = new File(configBase, files[i]);

        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
            ContextName cn = new ContextName(files[i], true);

            if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                continue;
			// 分别为每个文件创建部署线程
            results.add(
                es.submit(new DeployDescriptor(this, cn, contextXml)));
        }
    }

    for (Future<?> result : results) {
        try {
            // 这里是要等所有文件部署线程都执行完才结束方法的
            result.get();
        } catch (Exception e) {
            log.error(sm.getString(
                "hostConfig.deployDescriptor.threaded.error"), e);
        }
    }
}

在具体的,对部署的细节,分别对应了相应的三个方法:deployDescriptor、deployWAR、deployDirectory。三个方法内部非常冗长,我这里不多做介绍,想再细致了解可以自己翻源码。这三个方法分别为每个应用(webapps文件夹 下面的一个war包或是一个文件夹,都分别属于一个应用)创建更进一层次的容器:Context。

九、上下文

到了Context(StandardContext)这一层,相对应的逻辑就多了起来了,源码也是惊人级别的,单独startInternal方法就有332行!到了这一层,我们就正式对一个应用(webapps下面的一个文件夹)进行初始化操作了。我没办法将三百多行的代码都列到这里,我先写一个流程列表,然后删减性的贴一贴代码。

1、Context的整体流程

  • 为应用初始化一个类加载器:WebappLoader(内部真是的加载器类是:ParallelWebappClassLoader)
  • JNDI和JMX注册
  • 触发CONFIGURE_START_EVENT事件使用ContextConfig进行回调,解析web.xml
  • 初始化web.xml中配置的Listener
  • 触发Listener中的contextInitialized方法
  • 初始化web.xml中配置的Filter方法
  • 读取web.xml中配置的load-on-startup的Servlet参数,封装在Wrapper里面
  • 调用StandardWrapper中的load()方法,将封装在Wrapper中的Servlet通过读取的参数进行初始化
  • 调用Servlet的init方法

2、startInternal代码片段

protected synchronized void startInternal() throws LifecycleException {

    ................
	// 初始化应用类加载器,这个我会在接下来的文章中详细描述
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

    ................

    try {
            ................
        //主要运行了ContextConfig中的configureStart方法,用来解析webxml这个文件,所以会慢
        fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
            ................
        if (ok) {
            // listener的触发
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

            ................
        if (ok) {
            // 初始化filter
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        
        if (ok) {
            // 这里初始化Servlet调用init方法
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

        
        super.threadStart();
    } finally {
        ................
    }

        ................
}

十、包装

对于Wrapper(StandardWrapper)来说,其实就是一个servlet的封装:web.xml读取出来的对于servlet的封装都先存储在Wrapper里面,然后通过load方法,进而调用loadServlet进行初始化操作:

@Override
public synchronized void load() throws ServletException {
    // 初始化Servlet
    instance = loadServlet();

    if (!instanceInitialized) {
        initServlet(instance);
    }

    // 如果是jsp的话要进行其他的jmx注册
    if (isJspServlet) {
        StringBuilder oname = new StringBuilder(getDomain());

        oname.append(":type=JspMonitor");

        oname.append(getWebModuleKeyProperties());

        oname.append(",name=");
        oname.append(getName());

        oname.append(getJ2EEKeyProperties());

        try {
            jspMonitorON = new ObjectName(oname.toString());
            Registry.getRegistry(null, null)
                .registerComponent(instance, jspMonitorON, null);
        } catch( Exception ex ) {
            log.info("Error registering JSP monitoring with jmx " +
                     instance);
        }
    }
}

十一、整体的调用时序图

至此我们把全部容器的初始化过串了一遍

十二、最后的规划

这篇文章虽然多,但是,是一个最基本的梗概。从整体上把启动过程串了一遍,具体还没有涉及很多细节。接下来我打算趁热打铁的出两篇文章,大体规划如下:

  • 正传:管道、阀、连接器的构建及其如何解析请求、封装请求、传递请求
  • 后传:Tomcat类加载器细节(打破双亲委派),JMX的简单涉及

国庆长假来了,祝大家玩得开心!

转载于:https://my.oschina.net/UBW/blog/2221891

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值