精华内容
下载资源
问答
  • 参考:https://blog.csdn.net/g6U8W7p06dCO99fQ3/article/details/98908681 看下调用栈
    展开全文
  • 更别提还有xml解析用到的Digester的细节,JMX对象管理的细节,Java用于安全的管理机制,线程池自定义的细节,网络连接NIO的使用等等,到我写文章之日,我本人只是对容器做了全面的启动源码分析,对于网络请求这一块...

    这次,想到写这个系列,完全算是一次意外:本来我是想买本书,好好研究下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

    展开全文
  • Tomcat源码分析

    2019-08-23 08:12:30
    理解Tomcat的架构架构图相关组件的介绍二 Tomcat 启动源码分析Tomcat启动流程图Tomcat的启动流程三. 相关组件说明1. Startup.bat2. catalina.bat3 .Bootstrap如何查看tomcat的Bootstrap类的源码 ?主要类的介绍总结4...

    通过学习Tomcat的架构 . 相关源码以及运行原理 ,更加深刻的去了解和掌握Tomcat的使用

    一 . 理解Tomcat的架构

    架构图

    在这里插入图片描述

    相关组件的介绍

    可以结合 Tomcat根目录下的conf目录下的server.xml来看

    1. Server

    Server 服务器的意思,代表整个tomcat 服务器,一个tomcat 只有一个Server
    Server 中包含至少一个Service 组件,用于提供具体服务。这个在配置文件中也得到很好的体现(port=“8005” shutdown="SHUTDOWN"是在8005 端口监听到"SHUTDOWN"命令,服务器就会停止)

    2 .Service

    Service 中的一个逻辑功能层, 一个Server 可以包含多个Service
    Service 接收客户端的请求,然后解析请求,完成相应的业务逻辑,然后把处理后的结果返回给客户端,一般会提供两个方法,一个start 打开服务Socket 连接,监听服务端口,一个stop 停止服务释放网络资源。

    3 .Connector

    称作连接器,是Service 的核心组件之一,一个Service 可以有多个Connector,主要是连接客户端请求,用于接受请求并将请求封装成Request 和Response,然后交给Container 进行处理,Container 处理完之后在交给Connector 返回给客户端。

    4 .Container

    Service 的另一个核心组件,按照层级有Engine,Host,Context,Wrapper 四种一个Service 只有一个Engine,其主要作用是执行业务逻辑

    5 .Engine

    一个Service 中有多个Connector 和一个Engine,Engine 表示整个Servlet 引擎,一个Engine 下面可以包含一个或者多个Host,即一个Tomcat 实例可以配置多个虚拟主机,默认的情况下conf/server.xml 配置文件中<Engine name="Catalina" defaultHost="localhost"> 定义了一个名为Catalina 的Engine。一个Engine 包含多个Host 的设计,使得一个服务器实例可以承担多个域名的服务

    6 .Host

    代表一个站点,也可以叫虚拟主机,一个Host 可以配置多个Context,在server.xml 文件中的默认配置为

    <Host name="localhost" appBase="webapps" unpackWARs="true"autoDeploy="true">
    
    其中appBase=webapps, 也就是<CATALINA_HOME>\webapps 目录,
    unpackingWARS=true 属性指定在appBase 指定的目录中的war 包都自动的解压,
    autoDeploy=true 属性指定对加入到appBase 目录的war 包进行自动的部署。
    

    7 .Context

    Context,代表一个应用程序,就是日常开发中的web 程序,或者一个WEB-INF 目录以及下面的web.xml 文件,换句话说每一个运行的webapp 最终都是以Context 的形式存在,每个Context 都有一个根路径和请求路径;与Host 的区别是Context 代表一个应用,如,默认配置下webapps 下的每个目录都是一个应用,其中ROOT 目录中存放主应用,其他目录存放别的子应用,而整个webapps 是一个站点。

    二 Tomcat 启动源码分析

    Tomcat启动流程图

    在这里插入图片描述

    Tomcat的启动流程

    tomcat 的启动流程很标准化,入口是 BootStrap,统一按照生命周期管理接口 Lifecycle 的定义进行启动。
    首先,调用 init()方法逐级初始化,接着调用 start()方法进行启动,同时, 每次调用伴随着生命周期状态变更事件的触发。

    三. 相关组件说明

    1. Startup.bat

    查看bin目录下的Startup.bat
    Startup.bat的主要作用是启动与他在相同目录的catalina.bat ,并且无需指定启动参数(start)

    2. catalina.bat

    查看bin目录下的catalina.bat

    set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
    set MAINCLASS=org.apache.catalina.startup.Bootstrap
    set ACTION=start
    

    总结 :
    由上面摘抄的主要源码可知 ,在catalina.bat 启动时 ,主要是加载了一个叫bootstrap.jar 的jar包 . 在加载这个jar 以后 ,他又运行了这个 jar 下面的org.apache.catalina.startup.Bootstrap 这个类, 通过set ACTION=start 启动他的main方法 ,完成对Tomcat的启动

    3 .Bootstrap

    如何查看tomcat的Bootstrap类的源码 ?

    • .通过maven仓库添加相应坐标 ,然后查看即可
    • .不过现在提供一种最简单的方式 , 找到一个下载 一个任意版本的 tomcat
    • 创建一个java项目 ,右击import ,选择file system 如下图 ,点击到tomcat对应的bin目录 ,finsh即可

    在这里插入图片描述

    • 导入完成后效果如下 :

    在这里插入图片描述

    • 根据 org.apache.catalina.startup.Bootstrap 这个绝对路径找到Bootstrap

    • ctrl +o 查看 ,该类的结构

    在这里插入图片描述

    主要组件说明

    main 方法
    是整个 tomcat 启动时的入口。在 main 方法中,使用 bootstrap.init()来初始化类加载器和创建 Catalina 实例,然后再启动 Catalina 线程

    bootstrap.init()方法
    用于初始化容器相关 ,首先创建类加载器, 然后通过反射创建 org.apache.catalina.startup.Catalina 实例。

    总结

    bootstrap的main方法首先会对类加载器 (classloader)进行初始化 ,然后调用load方法去初始化catalina ,然后catalina 去创建并初始化servler …直到最后初始化 Protocol handler .然后bootstrap再调用catalina 的start的方法 …依次类推,如下图

    在这里插入图片描述

    4 Catalina

    类结构图

    在这里插入图片描述

    主要组件说明

    Lifecycle 接口

    Lifecycle 提供一种统一的管理对象生命周期的接口。通过 Lifecycle、LifecycleListener、LifecycleEvent,Catalina 实现了对 tomcat 各种组件、容器统一的启动和停止的方式。
    在 Tomcat 服 务 开 启 过 程 中 启 动 的 一 些 列 组 件 、 容 器 , 都 实 现 了org.apache.catalina.Lifecycle 这个接口,其中的 init()、start() 方法、stop() 方法,为其子类实 现了统一的 start 和 stop 管理

    load 方法解析 server.xml 配置文件

    load 方法解析 server.xml 配置文件,并加载 Server、Service、Connector、Container、Engine、 Host、Context、Wrapper 一系列的容器。加载完成后,调用 initialize()来开启一个新的 Server

    Digester 类解析 server.xml 文件

    利用 Digester 类解析 server.xml 文件,得到容器的配置。

    demon.start()

    demon.start()方法会调用 Catalina 的 start 方法
    Catalina 实例执行 start 方法。这里有两个点,一个是 load()加载 server.xml 配置、初始 化 Server 的过程,一个是 getServer().start()开启服务、初始化并开启一系列组件、子容器的 过程

    5 StandardServer

    类结构图

    在这里插入图片描述

    主要组件说明

    service.initialize()

    然后拿到 StandardServer 实例调用 initialize()方法初始化 Tomcat 容器的一系列组件。一 些容器初始化的的时候,都会调用其子容器的 initialize()方法,初始化它的子容器。顺序是 StandardServer、StandardService、StandardEngine、Connector。每个容器都在初始化自身相 关设置的同时,将子容器初始化。

    为什么启动Catalina 时 ,没有直接调用start的方法 ,而是使用反射来调用呢?

       /**
         * Start the Catalina daemon.
         */
        public void start()
            throws Exception {
            if( catalinaDaemon==null ) init();
    
            Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
            method.invoke(catalinaDaemon, (Object [])null);
    
        }
    

    原因 :
    因为Catalina 没有继承 LifeCycle这个接口 ,而LifeCycle规定了start ,stop等相关的方法 .所以他不能像StandardService 或StandardServer一样可以直接调用start 方法.
    在这里插入图片描述

    展开全文
  • Tomcat作为一款经典的Web容器,一直经久不衰,接下来我们从源码角度来看一下Tomcat启动流程,了解其工作的原理,并看看其在设计编码上有哪些思想值得我们借鉴。 当然在开始之前还是要先强调一下源码的阅读方式:①...
    • 概述
    • Tomcat各组件初始化过程整体概览
    • Tomcat各组件启动过程整体概览
    • Tomcat的启停
    • 各组件与实现类的加载与启动

    概述

    • Tomcat作为一款经典的Web容器,一直经久不衰,接下来我们从源码角度来看一下Tomcat的启动流程,了解其工作的原理,并看看其在设计编码上有哪些思想值得我们借鉴。
    • 当然在开始之前还是要先强调一下源码的阅读方式:①熟练使用这个工具;②把握整体架构,设计模式等等;③不要深入研究具体的代码实现(除非做二次开发),以全局思路与功能实现为主;④一定要将源代码跑起来调试;⑤接口与抽象类是要重点关注的,在接口中往往能看到很多全局的设计思路;而抽象类是具体公共点的抽取,变化部分交给子类实现。

    Tomcat各组件初始化过程整体概览

    • 完善中

    Tomcat各组件启动过程整体概览

    • 完善中

    Tomcat的启停

    • 由于Tomcat也是由Java实现的程序,因此Tomcat启动必然会依赖main函数的启动。
    • 我们Tomcat一般都是通过脚本来启动(Win:catalina.bat;Linux:catalina.sh),在启动Tomcat的时候,会从控制台传入一个“start”的参数,main函数如果接收到“start”参数,就进入Tomcat各组件的初始化和启动阶段。
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\startup\Bootstrap.java
    //Tomcat的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 {
                    //在init()中的操作:
                    //1.初始化Tomcat自身组件的一些类加载器(common、shared、server)
                    //2.反射创建“org.apache.catalina.startup.Catalina”对象,并赋值给Bootstrap的catalinaDaemon成员属性
                    //3.反射调用Catalina的setParentClassLoader方法,传递sharedLoader参数;
                    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 {
            //参数默认为start
            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")) {
                //是否阻塞当前唯一的非守护线程(持续监听Tomcat配置的SHUTDOWN端口)
                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);
        }
    }
    • 在Bootstrap启动时反射调用的Catalina.java的start方法中,当其他组件都启动加载完成后,就会调用await方法,如果不是其他框架内嵌Tomcat的话,就会新建一个socket持续监听配置文件中配置的SHUTDOWN端口,这也起到阻塞主线程的作用(其他线程都是守护线程,这样的好处是当主线程退出时,Tomcat可以有效地立即退出),我们可以通过连接上配置的SHUTDOWN端口去关闭Tomcat(telnet)。
    • 当然如果我们直接通过catalina脚本文件并使用“stop”参数来关闭Tomcat,其流程依旧为运行Tomcat的main函数,加载配置文件,拿到SHUTDOWN的端口与命令,然后通过socket连接去关闭(与手动连接SHUTDOWN端口关闭一致)。
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\core\StandardServer.java的await方法
    //通过socket连接SHUTDOWN端口关闭Tomcat的实现
       s = getServer();
       if (s.getPort()>0) {
           try (Socket socket = new Socket(s.getAddress(), s.getPort());
                   OutputStream stream = socket.getOutputStream()) {
               String shutdown = s.getShutdown();
               for (int i = 0; i < shutdown.length(); i++) {
                   stream.write(shutdown.charAt(i));
               }
               stream.flush();
           } catch (ConnectException ce) {
           ......
    • 当然在很多时候,可能并不是我们主动发出的停止Tomcat的命令,其可能是操作系统(收到中断请求)也可能是程序中某个线程出现致命错误需要停止服务;这个时候就会使用到JDK提供的“SHOUDOWN HOOK”,Java的关闭钩子;我们的Tomcat在启动时(Catalina的start()方法中)也会注册上“SHOUDOWN HOOK”,在程序停止前就会运行“SHOUDOWN HOOK”中的方法,当然“SHOUDOWN HOOK”中的方法肯定是不太耗时的,否则可能中断时间过长超时后还是可能被强行停止程序。
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\startup\Catalina.java的start方法
    //Catalina在启动时注册“SHOUDOWN HOOK”的源码
        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);
            }
        }
    
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\startup\Catalina.java
    //“SHOUDOWN HOOK”具体实现
    // XXX Should be moved to embedded !
    /**
     * Shutdown hook which will perform a clean shutdown of Catalina if needed.
     */
    protected class CatalinaShutdownHook extends Thread {
    
        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }
    • 由于Tomcat的类加载机制,Bootstrap.java与Catalina.java并不是同一个类加载器加载的,因此Bootstrap.java调用Catalina.java中的方法全都使用的反射调用。

    各组件与实现类的加载与启动

    • Tomcat的各组件的加载与启动使用到了责任链设计模式,即每一个组件都会向下负责一个或者多个组件的加载与启动及其生命周期。
    • Tomcat的各组件的生命周期管理与组件的初始化还用到了模板设计模式,其生命周期管理都来自其父类LifecycleBase中,并且由父类中调用initInternal()方法进行组件初始化,此方法在子类中均有相应实现。
    • Tomcat的初始化各个组件与启动各个组件是两条链路,即在完成各个组件的初始化之后,才会进行各个组件的启动流程。

    Catalina的加载与初始化

    • 在Catalina的load()方法中,主要做了三件事情:(1)创建xml的解析器(Digester)解析server.xml;(2)创建StandardServer实例 (server)以及其他server.xml配置涉及的诸多实例;(3)初始化server对象(StandardServer实例)。
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\startup\Catalina.java
    /**
     * Start a new server instance.
     */
    public void load() {
    
        if (loaded) {
            return;
        }
        loaded = true;
    
        long t1 = System.nanoTime();
    
        initDirs();
    
        // Before digester - it may be needed
        initNaming();
    
        //创建xml解析器解析server.xml,方法内部会创建大量相关的对象
        Digester digester = createStartDigester();
    
        //此处省略各种文件流的校验
        ......
    
        //将当前Catalina对象赋值给StandardServer对象
        getServer().setCatalina(this);
        //加载我们配置的应用程序路径
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    
        // Stream redirection
        initStreams();
    
        // Start the new server
        try {
            //初始化StandardServer对象
            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");
        }
    }
    • TODO:启动========================

    Server(StandardServer)的加载与初始化

    • 还是责任链模式的启动方式,StandardServer的初始化是由Catalina的load()方法中的getServer().init()触发的;从StandardServer开始,其组件的生命周期被统一管理(LifeCycleBase),这是典型的模板方法设计模式;在LifeCycleBase抽象类的init()方法中,会调用initInternal()方法,该方法也是一个抽象方法,在相应的组件中会有实现。
    • 在StandardSever的initInternal()方法中,主要做了这几件事:①调用Mbean父类(LifeCycleMbeanBase)的initInternal();②注册StringCache、注册MBeanFactory用于管理Server初始化全局资源;③通过Catalina中的成员变量parentClassLoader指向的SharedClassLoader加载Jar包;④进行StandardServer管理的多个services的初始化操作。
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\core\StandardServer.java
    protected void initInternal() throws LifecycleException {
        //Mbean父类(LifeCycleMbeanBase)的初始化
        super.initInternal();
    
        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(), "type=StringCache");
    
        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
    
        // Register the naming resources
        globalNamingResources.init();
    
        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            
            //省略Jar包加载流程
            ......
        }
        
        //进行StandardServer管理的多个services的初始化操作
        for (Service service : services) {
            service.init();
        }
    }
    • TODO:启动========================

    Service(StandardService)的加载与初始化

    • Service的初始化还是与之前一样,由LifeCycleBase抽象类调用initInternal(),StandardService会对其进行实现;
    • StandardService中管理的组件就会多一些,我们先来查看一下其成员变量:
    //源码位于:.\apache-tomcat-8.5.57-src\java\org\apache\catalina\core\StandardService.java
    public class StandardService extends LifecycleMBeanBase implements Service {
    
        /**
         * The <code>Server</code> that owns this Service, if any.
         */
        private Server server = null;
    
    
        /**
         * 多个连接器
         */
        protected Connector connectors[] = new Connector[0];
      
        /**
         * 线程池
         */
        protected final ArrayList<Executor> executors = new ArrayList<>();
    
        private Engine engine = null;
    
        private ClassLoader parentClassLoader = null;
    
        /**
         * Mapper.
         */
        protected final Mapper mapper = new Mapper();
    
    
        /**
         * Mapper listener.
         */
        protected final MapperListener mapperListener = new MapperListener(this);
    • 在StandardService中的initInternal()方法中,主要做了这几件事:①调用Mbean父类(LifeCycleMbeanBase)的initInternal();②
    展开全文
  • Tomcat启动过程分析 Tomcat默认server.xml文件 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup....
  • Tomcat源码系列: 本章为Tomcat初始化流程的第一章,主要内容为Engine启动分支
  • Tomcat7.0源码分析

    2017-09-15 12:10:00
    1:Tomcat7.0源码分析——类加载体系 2:Tomcat7.0源码分析——server.xml文件的加载与解析 ...4:Tomcat7.0源码分析——启动与停止服务原理 5:Tomcat7.0源码分析——请求原理分析(上) 6:...
  • Tomcat源码系列: 本章为Tomcat初始化流程的第二章,主要内容为Connector的启动分支.
  • tomcat7源码分析

    千次阅读 2017-01-16 16:21:14
    tomcat启动流程源码分析 tomcat处理一个请求过程分析 .jsp生成.java和.class流程分析 apache beachmark性能测试 一、tomcat基本框架由三部分组成: 组件架构:组件搭起房梁 基于JMX:JMX管理组件等
  • 首先,我们复制启动Tomcat时候Console窗口打印的日志,可以见到一些关键路径,个人认为比较重要的地方标注了出来: 八月 14, 2019 3:23:50 下午 org.apache.catalina.startup.VersionLoggerListener log 信息: ...
  • 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅《Tomcat源码分析二:先看看Tomcat的整体架构》一文。 先看看应用情况 在《Servlet与Tomcat运行示例》一文中,我详细的记录了Tomcat是...
  • 前言不得不说SpringBoot的开发者是在为大众程序员谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springboot是怎么启动的呢?内置tomcat开发阶段对我们来说使用...
  •  我们都知道只需要在Tomcat中bin目录下启动startup.bat/sh,那么整个Tomcat就可以启动起来给我们提供服务,我们不免心生疑问启动startup.bat/sh以后,Tomcat到底是如何启动的,那么下面我们就来一步一步分析吧!...
  • tomcat 8 源码分析 ,本文主要讲解tomcat拥有哪些组件,容器,又是如何启动的 推荐访问我的个人网站,排版更好看: https://chenmingyu.top/tomcat-source-code/ tomcat 简介 ​ Tomcat是Apache 软件基金会(Apache...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,143
精华内容 457
关键字:

tomcat启动源码分析