TOMCAT源码分析——启动服务
熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的。对于startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程。
由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与停止进行分析。
启动过程分析我们启动Tomcat的命令如下:
sh startup.sh
所以,将从shell脚本startup.sh开始分析Tomcat的启动过程。startup.sh的脚本代码见代码清单1。
代码清单1
os400=false case "`uname`" in OS400*) os400=true;; # resolve links - $0 may be a softlink PRG="$0" while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : .*- \(.*\)$` if expr "$link" : /.* /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" PRGDIR=`dirname "$PRG"` EXECUTABLE=catalina.sh # Check that target executable exists if $os400; then # -x will Only work on the os400 if the files are: # 1. owned by the user # 2. owned by the PRIMARY group of the user # this will not work if the user belongs in secondary groups eval if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then echo "Cannot find $PRGDIR/$EXECUTABLE" echo "The file is absent or does not have execute permission" echo "This file is needed to run this program" exit 1 exec "$PRGDIR"/"$EXECUTABLE" start "$@"
代码清单1中有两个主要的变量,分别是:
PRGDIR:当前shell脚本所在的路径;
EXECUTABLE:脚本catalina.sh。
根据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" start "$@",我们知道执行了shell脚本catalina.sh,并且传递参数start。catalina.sh中接收到start参数后的执行的脚本分支见代码清单2。
代码清单2
elif [ "$1" = "start" ] ; then # 此处省略参数校验的脚本 shift touch "$CATALINA_OUT" if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" shift eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ "$CATALINA_OUT" 2 1 " " else eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ "$CATALINA_OUT" 2 1 " " if [ ! -z "$CATALINA_PID" ]; then echo $! "$CATALINA_PID" echo "Tomcat started."
从代码清单2可以看出,最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。Bootstrap的main方法的实现见代码清单3。
代码清单3
/** * Main method, used for testing only. * @param args Command line arguments to be processed public static void main(String args[]) { if (daemon == null) { // Dont set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { t.printStackTrace(); return; daemon = bootstrap; 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(); } else if (command.equals("stop")) { daemon.stopServer(args); } else { log.warn("Bootstrap: command \"" + command + "\" does not exist."); } catch (Throwable t) { t.printStackTrace(); }
从代码清单3可以看出,当传递参数start的时候,command等于start,此时main方法的执行步骤如下:
步骤一 初始化BootstrapBootstrap的init方法(见代码清单4)的执行步骤如下:
1.设置Catalina路径,默认为Tomcat的根目录;
2.初始化Tomcat的类加载器,并设置线程上下文类加载器(具体实现细节,读者可以参考《TOMCAT源码分析——类加载体系》一文);
3.用反射实例化org.apache.catalina.startup.Catalina对象,并且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外)。
代码清单4
/** * Initialize daemon. public void init() throws Exception // Set Catalina path setCatalinaHome(); setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class ? startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader 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); catalinaDaemon = startupInstance; }步骤二 加载、解析server.xml配置文件
当传递参数start的时候,会调用Bootstrap的load方法(见代码清单5),其作用是用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件,具体细节已在《TOMCAT源码分析——SERVER.XML文件的加载与解析》一文中详细介绍,有兴趣的朋友可以选择阅读。
代码清单5
/** * Load daemon. 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); }步骤三 启动Tomcat
当传递参数start的时候,调用Bootstrap的load方法之后会接着调用start方法(见代码清单6)启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法。
代码清单6
/** * 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的start方法(见代码清单7)的执行步骤如下:
1.验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer。
2.启动Server容器,有关容器的启动过程的分析可以参考《TOMCAT源码分析——生命周期管理》一文的内容。
3.设置关闭钩子。这么说可能有些不好理解,那就换个说法。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要在退出的时候做一些清理工作,比如:内存清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递给Runtime的addShutdownHook方法即可。
4.最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令。
5.如果Tomcat运行正常且没有收到shutdown命令,是不会向下执行stop方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat。
代码清单7
/** * Start a new server instance. public void start() { 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 { getServer().start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); long t2 = System.nanoTime(); if(log.isInfoEnabled()) log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try { // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULIs shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULIs hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. if (await) { await(); stop(); }
Catalina的await方法(见代码清单8)实际只是代理执行了Server容器的await方法。
代码清单8
/** * Await and shutdown. public void await() { getServer().await(); }
以Server的默认实现StandardServer为例,其await方法(见代码清单9)的执行步骤如下:
1.创建socket连接的服务端对象ServerSocket;
2.循环等待接收客户端发出的命令,如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待。
代码清单9
public void await() { // Negative values - dont wait on port - tomcat is embedded or we just dont like ports gja if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; if( port==-1 ) { while( true ) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { if( stopAwait ) return; // Set up a server socket to wait on ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName(address)); } catch (IOException e) { log.error("StandardServer.await: create[" + address + ":" + port + "]: ", e); System.exit(1); // Loop waiting for a connection and a valid command while (true) { // Wait for the next connection Socket socket = null; InputStream stream = null; try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (AccessControlException ace) { log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { log.error("StandardServer.await: accept: ", e); System.exit(1); // Read a set of characters from the socket StringBuilder command = new StringBuilder(); int expected = 1024; // Cut off to avoid DoS attack while (expected shutdown.length()) { if (random == null) random = new Random(); expected += (random.nextInt() % 1024); while (expected 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { log.warn("StandardServer.await: read: ", e); ch = -1; if (ch 32) // Control character or EOF terminates loop break; command.append((char) ch); expected--; // Close the socket now that we are done with it try { socket.close(); } catch (IOException e) { // Ignore // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { log.info(sm.getString("standardServer.shutdownViaPort")); break; } else log.warn("StandardServer.await: Invalid command " + command.toString() + " received"); // Close the server socket and return try { serverSocket.close(); } catch (IOException e) { // Ignore }
至此,Tomcat启动完毕。很多人可能会问,执行sh shutdown.sh脚本时,是如何与Tomcat进程通信的呢?如果要与Tomcat的ServerSocket通信,socket客户端如何知道服务端的连接地址与端口呢?请阅读《TOMCAT源码分析——停止服务》一文。
SpringBoot源码分析系列之四:如何启动内嵌Tomcat SpringBoot相信很多同学都非常了解,实际工作中也经常使用到。但是不知道大家在使用过程中有没有想过一个问题,SpringBoot内嵌tomcat到底是怎么启动的?内嵌tomcat启动服务的好处又是什么呢?本文将结合SpringBoot源码探讨下这些问题。
http://www.cnblogs.com/fangjian0423/p/servletContainer-tomcat-urlPattern.html#springmvc
相关文章
- Tomcat环境配置(超级简单)[通俗易懂]
- Tomcat下载——tomcat7、tomcat8、tomcat9官网下载链接
- Linux tomcat部署War包,Linux在Tomcat部署JavaWeb项目,Linux部署War包
- Linux服务器tomcat部署war包「建议收藏」
- 漏洞复现 - - -Tomcat弱口令漏洞
- Tomcat使用IDEA远程Debug调试[通俗易懂]
- Maven配置Tomcat_maven和tomcat的区别
- idea web项目部署到tomcat_系统部署步骤
- Tomcat配置域名_tomcat nginx
- 从Tomcat源码中寻找request路径进行注入
- SpringBoot源码分析系列之一:如何启动内嵌Tomcat
- Nginx+Tomcat实现动静分离详解程序员
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]编程语言
- Linux系统下快速关闭Tomcat服务(linux关闭tomcat)
- spring boot上传文件错误The temporary upload location [/tmp/tomcat.******/work/Tomcat/localhost/ROOT] is not valid详解编程语言
- MySQL与Tomcat的完美结合(mysql和tomcat)
- 改变Linux Tomcat的端口号:一步一步来(linux修改tomcat端口号)
- Linux下一步步搭建Tomcat环境(linux配置tomcat环境变量)
- 搭建Tomcat连接MySQL数据库的快速指南(tomcat连接mysql数据库)
- 安装Linux上的Tomcat服务器(linux安装tomcat)
- 检查Linux服务器上Tomcat是否启动(linux查看tomcat是否启动)
- Linux部署Tomcat项目:一步一步步入正轨(linux部署tomcat项目)
- 日志Linux下查看Tomcat日志的方法(linux查看tomcat)
- Linux系统下部署高性能Tomcat服务器(linux部署tomcat)
- Tomcat Session集群
- Nginx + Tomcat 有关SSI 的那些事儿