2019-10-28 17:44  阅读(2370)
文章分类:Tomcat 源码分析 文章标签:TomcatTomcat 源码
©  原文作者:clawhub 原文地址:http://clawhub.club

Tomcat启动入口就在脚本startup.sh中,具体脚本可以看tomcat的源码,这个启动脚本主要用来判断环境,
找到catalina.sh脚本路径,将启动参数传递给catalina.sh执行。
catalina.sh start 最终会执行org.apache.catalina.startup.Bootstrap中的main方法,并把start参数传入。
以后分析Tomcat关闭的时候,也是一个套路,最终都会调用到org.apache.catalina.startup.Bootstrap的main方法,并把stop参数传入。

分析main方法:

           /**
             * 通过提供的脚本启动Tomcat时的主方法和入口点。
             *
             * @param args 要处理的命令行参数
             */
            public static void main(String args[]) {
    
                //main使用的守护进程对象。
                synchronized (daemonLock) {
                    //daemon是volatile修饰的Bootstrap对象
                    if (daemon == null) {
                        //在init()完成之前不要设置守护进程
                        Bootstrap bootstrap = new Bootstrap();
                        try {
                            //初始化守护进程。
                            bootstrap.init();
                        } catch (Throwable t) {
                            handleThrowable(t);
                            t.printStackTrace();
                            return;
                        }
                        daemon = bootstrap;
                    } else {
                        // W当作为服务运行时,要停止的调用将位于新线程上,
                        // 因此请确保使用了正确的类加载器,以防止出现一系列类未找到异常。直到init()完成
                        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")) {
                        //启动操作
                        //通过反射调用守护进程引用的org.apache.catalina.startup.Catalina实例的setAwait方法
                        daemon.setAwait(true);
                        //调用Catalina实例的load方法
                        daemon.load(args);
                        //start方法
                        daemon.start();
                        //反射调用Catalina实例的getServer方法,返回的对象为空时,终止当前运行的Java虚拟机。
                        if (null == daemon.getServer()) {
                            System.exit(1);
                        }
                    } else if (command.equals("stop")) {
                        //通过反射调用Catalina的stopServer方法。
                        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);
                }
    
            }
    

启动过程有两步操作:
1、初始化守护进程,获取类加载器和反射实例化org.apache.catalina.startup.Catalina。
2、根据启动参数start,通过反射,依次执行Catalina实例的setAwait、load、start方法。

下面主要分析Catalina实例的setAwait、load、start方法:

1 setAwait

入参为true

        /**
        * Use await.
        */
        protected boolean await = false;
    
        public void setAwait(boolean b) {
            await = b;
        }
    

2 load

         /**
             * 启动一个新的服务器实例。
             */
            public void load() {
                //防止重复加载。
                if (loaded) {
                    return;
                }
                loaded = true;
                long t1 = System.nanoTime();
                //创建java.io.tmpdir文件夹
                initDirs();
    
                // Before digester - it may be needed
                //初始化jmx的环境变量
                initNaming();
    
                // Create and execute our Digester
                //创建和配置将用于启动的Digester。
                //配置解析server.xml中各个标签的解析类
                Digester digester = createStartDigester();
    
                InputSource inputSource = null;
                InputStream inputStream = null;
                File file = null;
                try {
    
                    //下面一大段都是为了加载conf/server.xml配置文件,失败就加载server-embed.xml
                    ...
                    try {
                        inputSource.setByteStream(inputStream);
                        //把Catalina作为一个顶级容器
                        digester.push(this);
                        //解析过程会实例化各个组件,比如Server、Container、Connector等
                        digester.parse(inputSource);
                    } catch (SAXParseException spe) {
                        log.warn("Catalina.start using " + getConfigFile() + ": " +
                                spe.getMessage());
                        return;
                    } catch (Exception e) {
                        log.warn("Catalina.start using " + getConfigFile() + ": ", e);
                        return;
                    }
                } finally {
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
    
                //这里的server在解析xml之后就有值了,这是Server的Catalina信息
                getServer().setCatalina(this);
                getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
                getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    
                // Stream redirection
                initStreams();
    
                // Start the new server
                try {
                    //生命周期init方法
                    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");
                }
            }
    

主要流程:

  • 初始化JMX环境变量
  • 创建和配置Digester
  • 读取配置文件conf/server.xml配置文件,失败就加载server-embed.xml
  • 通过Digester解析配置文件,并将当前Catalina作为最顶层容器,解析过程会实例化各种组件
  • 设置Server组件的catalina信息
  • 调用Server组件的生命周期init方法

解析配置文件,注入catalina的各种组件后面分析。
下面看start方法:

3 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();
                // Start the new server
                try {
                    //调用Server的start方法
                    getServer().start();
                } catch (LifecycleException e) {
                    log.fatal(sm.getString("catalina.serverStartFail"), e);
                    try {
                        //异常的时候调用Server的destroy方法
                        getServer().destroy();
                    } catch (LifecycleException e1) {
                        log.debug("destroy() failed for failed Server ", e1);
                    }
                    return;
                }
                long t2 = System.nanoTime();
                if (log.isInfoEnabled()) {
                    log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
                }
    
                // Register shutdown hook
                //注册关闭钩子
                if (useShutdownHook) {
                    if (shutdownHook == null) {
                        // Catalina.this.stop();
                        shutdownHook = new CatalinaShutdownHook();
                    }
    
                    Runtime.getRuntime().addShutdownHook(shutdownHook);
    
                    // If JULI is being used, disable JULI's shutdown hook since
                    // shutdown hooks run in parallel and log messages may be lost
                    // if JULI's hook completes before the CatalinaShutdownHook()
                    LogManager logManager = LogManager.getLogManager();
                    if (logManager instanceof ClassLoaderLogManager) {
                        ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                                false);
                    }
                }
                // Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
                if (await) {
                    //等待收到正确的关机命令,然后返回。
                    await();
                    //停止现有的服务器实例。
                    stop();
                }
            }
    

流程:

  • 调用Server组件的start方法,开启一个新的服务。
  • 注册关闭钩子
  • 让Tomcat在shutdown端口阻塞监听关闭命令

本篇目的就是了解整个Tomcat启动的主干流程,体现在代码层的就是依次执行Catalina实例的setAwait、load、start方法。
其中的load方法中的解析配置文件与注册组件、执行生命周期方法init;
start方法中的开启服务、注册关闭钩子、阻塞监听关闭指令等详细细节,将在后期慢慢分析。


来源:https://www.jianshu.com/u/9632919f32c3

点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Tomcat源码分析【四】启动过程分析之主干流程
上一篇
Tomcat源码分析【三】分析方向选择
下一篇
Tomcat源码分析【五】启动过程分析之配置文件解析与组件注入