Tomcat源码解析-启动过程分析之主干流程

Posted 幽暗森林之猪大屁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat源码解析-启动过程分析之主干流程相关的知识,希望对你有一定的参考价值。

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方法:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
       * 通过提供的脚本启动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
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
/**
   * Use await.
   */
   protected boolean await = false;
 
   public void setAwait(boolean b) {
       await = b;
   }

2 load

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
        * 启动一个新的服务器实例。
        */
       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

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
         * 启动一个新的服务器实例。
         */
        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方法中的开启服务、注册关闭钩子、阻塞监听关闭指令等详细细节,将在后期慢慢分析。
更多技术资讯可关注:itheimaGZ获取

以上是关于Tomcat源码解析-启动过程分析之主干流程的主要内容,如果未能解决你的问题,请参考以下文章

Android源代码解析之(十三)-->apk安装流程

Android应用启动流程分析

Android应用启动流程分析

Tomcat启动过程源码解读

linux内核源码解析03–启动代码分析之主内核页表创建

TOMCAT源码分析(启动框架)