Tomcat中各个组件的生命周期是由server控制的。那么server的生命周期由谁控制呢?
这个main方法首先会new一个Bootstrap对象,并且把这个对象放到一个static域中。
然后会对这个对象进行初始化,初始化方法如下:
我们之前提到过tomcat中server用来管理整个tomcat的生命周期
server start之后,catalina会创建一个 CatalinaShutdownHook 对象,然后将它添加到运行时的shutdownHook中。即注册一个虚拟机的shutdown hook,这样在我们不通过脚本关闭tomcat,而是直接杀死进程的时候,也能够执行catalina的stop方法,完成tomcat的关闭过程。
2、java虚拟机被终止,包括被用户中断,如:^C,或者系统级的事件:用户登出、系统关闭等。
发生上面的情况的时候,虚拟机就会调用所有的shutdown hook。
综上,tomcat启动过程实际就是bootstrap调用catalina的start方法,然后catalina调用server的start方法。关闭的时候,可以是bootstrap的stop方法调用catalina的stop方法,也可以是Hook调用catalina的stop方法,然后catalina的stop方法调用server的stop方法。
我们先来看下使用脚本启动tomcat的时候,首先会发生什么。
java应用要运行,需要一个main方法。tomcat启动的时候调用的是bootstrap中的main方法。
01 | /** |
02 | * Main method, used for testing only. |
03 | * |
04 | * @param args Command line arguments to be processed |
05 | */ |
06 | public static void main(String args[]) { |
07 |
08 | if (daemon == null ) { |
09 | // Don't set daemon until init() has completed |
10 | Bootstrap bootstrap = new Bootstrap(); |
11 | try { |
12 | bootstrap.init(); |
13 | } catch (Throwable t) { |
14 | handleThrowable(t); |
15 | t.printStackTrace(); |
16 | return ; |
17 | } |
18 | daemon = bootstrap; |
19 | } |
20 |
21 | try { |
22 | String command = "start" ; |
23 | if (args.length > 0 ) { |
24 | command = args[args.length - 1 ]; |
25 | } |
26 |
27 | if (command.equals( "startd" )) { |
28 | args[args.length - 1 ] = "start" ; |
29 | daemon.load(args); |
30 | daemon.start(); |
31 | } else if (command.equals( "stopd" )) { |
32 | args[args.length - 1 ] = "stop" ; |
33 | daemon.stop(); |
34 | } else if (command.equals( "start" )) { |
35 | daemon.setAwait( true ); |
36 | daemon.load(args); |
37 | daemon.start(); |
38 | } else if (command.equals( "stop" )) { |
39 | daemon.stopServer(args); |
40 | } else if (command.equals( "configtest" )) { |
41 | daemon.load(args); |
42 | if ( null ==daemon.getServer()) { |
43 | System.exit( 1 ); |
44 | } |
45 | System.exit( 0 ); |
46 | } else { |
47 | log.warn( "Bootstrap: command \"" + command + "\" does not exist." ); |
48 | } |
49 | } catch (Throwable t) { |
50 | // Unwrap the Exception for clearer error reporting |
51 | if (t instanceof InvocationTargetException && |
52 | t.getCause() != null ) { |
53 | t = t.getCause(); |
54 | } |
55 | handleThrowable(t); |
56 | t.printStackTrace(); |
57 | System.exit( 1 ); |
58 | } |
59 |
60 | } |
然后会对这个对象进行初始化,初始化方法如下:
01 | /** |
02 | * Initialize daemon. |
03 | */ |
04 | public void init() |
05 | throws Exception |
06 | { |
07 |
08 | // Set Catalina path |
09 | setCatalinaHome(); |
10 | setCatalinaBase(); |
11 |
12 | initClassLoaders(); |
13 |
14 | Thread.currentThread().setContextClassLoader(catalinaLoader); |
15 |
16 | SecurityClassLoad.securityClassLoad(catalinaLoader); |
17 |
18 | // Load our startup class and call its process() method |
19 | if (log.isDebugEnabled()) |
20 | log.debug( "Loading startup class" ); |
21 | Class<?> startupClass = |
22 | catalinaLoader.loadClass |
23 | ( "org.apache.catalina.startup.Catalina" ); |
24 | Object startupInstance = startupClass.newInstance(); |
25 |
26 | // Set the shared extensions class loader |
27 | if (log.isDebugEnabled()) |
28 | log.debug( "Setting startup class properties" ); |
29 | String methodName = "setParentClassLoader" ; |
30 | Class<?> paramTypes[] = new Class[ 1 ]; |
31 | paramTypes[ 0 ] = Class.forName( "java.lang.ClassLoader" ); |
32 | Object paramValues[] = new Object[ 1 ]; |
33 | paramValues[ 0 ] = sharedLoader; |
34 | Method method = |
35 | startupInstance.getClass().getMethod(methodName, paramTypes); |
36 | method.invoke(startupInstance, paramValues); |
37 |
38 | catalinaDaemon = startupInstance; |
39 |
40 | } |
在这个init方法中,会new一个catalina对象。bootstrap的大多数操作,诸如start、stop、load等,实际上都是调用的这个catalina对象的相应方法。
在bootstrap的初始化完成之后,会根据用户输入的main函数的参数(start、stop等),判断执行什么操作。
如果是start参数,就表明是启动tomcat。会先后执行daemon.setAwait、daemon.load、daemon.start这三个方法。他们最终会分别采用反射的方式去调用之前已经初始化的catalina的对应方法setAwait、load、start。也就是说其实bootstrap就调用了catalina的方法,本身是没做什么额外动作的。
stop则表明是关闭tomcat。
1 | /** |
2 | * Daemon object used by main. |
3 | */ |
4 | private static Bootstrap daemon = null ; |
这里需要注意一点,我们使用脚本启动和关闭tomcat的时候,实际上最终都是执行bootstrap的main方法,正因为daemon是static的,所以,我们start和stop的时候,实际上操作的是同一个bootstrap对象,才能对同一个tomcat的启动和关闭。
如上面所说,在启动过程中,daemon.load会调用catalina的load方法。catalina的load方法如下:
001 | /** |
002 | * Start a new server instance. |
003 | */ |
004 | public void load() { |
005 |
006 | long t1 = System.nanoTime(); |
007 |
008 | initDirs(); |
009 |
010 | // Before digester - it may be needed |
011 |
012 | initNaming(); |
013 |
014 | // Create and execute our Digester |
015 | Digester digester = createStartDigester(); |
016 |
017 | InputSource inputSource = null ; |
018 | InputStream inputStream = null ; |
019 | File file = null ; |
020 | try { |
021 | file = configFile(); |
022 | inputStream = new FileInputStream(file); |
023 | inputSource = new InputSource( "file://" + file.getAbsolutePath()); |
024 | } catch (Exception e) { |
025 | if (log.isDebugEnabled()) { |
026 | log.debug(sm.getString( "catalina.configFail" , file), e); |
027 | } |
028 | } |
029 | if (inputStream == null ) { |
030 | try { |
031 | inputStream = getClass().getClassLoader() |
032 | .getResourceAsStream(getConfigFile()); |
033 | inputSource = new InputSource |
034 | (getClass().getClassLoader() |
035 | .getResource(getConfigFile()).toString()); |
036 | } catch (Exception e) { |
037 | if (log.isDebugEnabled()) { |
038 | log.debug(sm.getString( "catalina.configFail" , |
039 | getConfigFile()), e); |
040 | } |
041 | } |
042 | } |
043 |
044 | // This should be included in catalina.jar |
045 | // Alternative: don't bother with xml, just create it manually. |
046 | if ( inputStream== null ) { |
047 | try { |
048 | inputStream = getClass().getClassLoader() |
049 | .getResourceAsStream( "server-embed.xml" ); |
050 | inputSource = new InputSource |
051 | (getClass().getClassLoader() |
052 | .getResource( "server-embed.xml" ).toString()); |
053 | } catch (Exception e) { |
054 | if (log.isDebugEnabled()) { |
055 | log.debug(sm.getString( "catalina.configFail" , |
056 | "server-embed.xml" ), e); |
057 | } |
058 | } |
059 | } |
060 |
061 |
062 | if (inputStream == null || inputSource == null ) { |
063 | if (file == null ) { |
064 | log.warn(sm.getString( "catalina.configFail" , |
065 | getConfigFile() + "] or [server-embed.xml]" )); |
066 | } else { |
067 | log.warn(sm.getString( "catalina.configFail" , |
068 | file.getAbsolutePath())); |
069 | if (file.exists() && !file.canRead()) { |
070 | log.warn( "Permissions incorrect, read permission is not allowed on the file." ); |
071 | } |
072 | } |
073 | return ; |
074 | } |
075 |
076 | try { |
077 | inputSource.setByteStream(inputStream); |
078 | digester.push( this ); |
079 | digester.parse(inputSource); |
080 | } catch (SAXParseException spe) { |
081 | log.warn( "Catalina.start using " + getConfigFile() + ": " + |
082 | spe.getMessage()); |
083 | return ; |
084 | } catch (Exception e) { |
085 | log.warn( "Catalina.start using " + getConfigFile() + ": " , e); |
086 | return ; |
087 | } finally { |
088 | try { |
089 | inputStream.close(); |
090 | } catch (IOException e) { |
091 | // Ignore |
092 | } |
093 | } |
094 |
095 | getServer().setCatalina( this ); |
096 |
097 | // Stream redirection |
098 | initStreams(); |
099 |
100 | // Start the new server |
101 | try { |
102 | getServer().init(); |
103 | } catch (LifecycleException e) { |
104 | if (Boolean.getBoolean( "org.apache.catalina.startup.EXIT_ON_INIT_FAILURE" )) { |
105 | throw new java.lang.Error(e); |
106 | } else { |
107 | log.error( "Catalina.start" , e); |
108 | } |
109 |
110 | } |
111 |
112 | long t2 = System.nanoTime(); |
113 | if (log.isInfoEnabled()) { |
114 | log.info( "Initialization processed in " + ((t2 - t1) / 1000000 ) + " ms" ); |
115 | } |
116 |
117 | } |
这个方法实际上是在加载、解析server.xml,生成tomcat的组件:server、service、connector、engine、host、context等。最后调用server的init方法。
我们之前提到过tomcat中server用来管理整个tomcat的生命周期
daemon.load方法执行之后,会执行daemon.start,这个方法调用catalina的start方法,catalina调用server的start方法,从而启动tomcat的各个组件。
01 | /** |
02 | * Start a new server instance. |
03 | */ |
04 | public void start() { |
05 |
06 | if (getServer() == null ) { |
07 | load(); |
08 | } |
09 |
10 | if (getServer() == null ) { |
11 | log.fatal( "Cannot start server. Server instance is not configured." ); |
12 | return ; |
13 | } |
14 |
15 | long t1 = System.nanoTime(); |
16 |
17 | // Start the new server |
18 | try { |
19 | getServer().start(); |
20 | } catch (LifecycleException e) { |
21 | log.error( "Catalina.start: " , e); |
22 | } |
23 |
24 | long t2 = System.nanoTime(); |
25 | if (log.isInfoEnabled()) { |
26 | log.info( "Server startup in " + ((t2 - t1) / 1000000 ) + " ms" ); |
27 | } |
28 |
29 | // Register shutdown hook |
30 | if (useShutdownHook) { |
31 | if (shutdownHook == null ) { |
32 | shutdownHook = new CatalinaShutdownHook(); |
33 | } |
34 | Runtime.getRuntime().addShutdownHook(shutdownHook); |
35 |
36 | // If JULI is being used, disable JULI's shutdown hook since |
37 | // shutdown hooks run in parallel and log messages may be lost |
38 | // if JULI's hook completes before the CatalinaShutdownHook() |
39 | LogManager logManager = LogManager.getLogManager(); |
40 | if (logManager instanceof ClassLoaderLogManager) { |
41 | ((ClassLoaderLogManager) logManager).setUseShutdownHook( |
42 | false ); |
43 | } |
44 | } |
45 |
46 | if (await) { |
47 | await(); |
48 | stop(); |
49 | } |
50 | } |
server start之后,catalina会创建一个 CatalinaShutdownHook 对象,然后将它添加到运行时的shutdownHook中。即注册一个虚拟机的shutdown hook,这样在我们不通过脚本关闭tomcat,而是直接杀死进程的时候,也能够执行catalina的stop方法,完成tomcat的关闭过程。
01 | /** |
02 | * Shutdown hook which will perform a clean shutdown of Catalina if needed. |
03 | */ |
04 | protected class CatalinaShutdownHook extends Thread { |
05 |
06 | @Override |
07 | public void run() { |
08 | try { |
09 | if (getServer() != null ) { |
10 | Catalina. this .stop(); |
11 | } |
12 | } catch (Throwable ex) { |
13 | ExceptionUtils.handleThrowable(ex); |
14 | log.error(sm.getString( "catalina.shutdownHookFail" ), ex); |
15 | } finally { |
16 | // If JULI is used, shut JULI down *after* the server shuts down |
17 | // so log messages aren't lost |
18 | LogManager logManager = LogManager.getLogManager(); |
19 | if (logManager instanceof ClassLoaderLogManager) { |
20 | ((ClassLoaderLogManager) logManager).shutdown(); |
21 | } |
22 | } |
23 | } |
24 | } |
The Java virtual machine shuts down in response to two kinds of events:
1、程序正常退出,包括最后一个non-daemon线程退出或者Runtime.exit、System.exit方法被调用。
2、java虚拟机被终止,包括被用户中断,如:^C,或者系统级的事件:用户登出、系统关闭等。
发生上面的情况的时候,虚拟机就会调用所有的shutdown hook。
综上,tomcat启动过程实际就是bootstrap调用catalina的start方法,然后catalina调用server的start方法。关闭的时候,可以是bootstrap的stop方法调用catalina的stop方法,也可以是Hook调用catalina的stop方法,然后catalina的stop方法调用server的stop方法。