项目build成功后,刷新整个项目,会发现多出一个output目录:java
为了让应用跑起来,能够检查一下output\build\conf下是否已经有配置文件,这些文件实际是从项目根路径conf目录下拷贝过来的。shell
找到BootStarp.java文件,Debug前加入默认的catalina home路径做为启动参数。apache
路径设置为output下build的绝对路径。好比我本身的机器设置的值是-Dcatalina.home="W:\workspace\tc7.0.70\output\build"bootstrap
这样就能够愉快的在文件中加入断点Debug源码分析了。运行以后的效果图:
OK,源码到此运行成功,完美~tomcat
上面运行源码用的BootStarp.java这个类中的main方法(后面再对这个main方法作分析),实际上咱们在用Tomcat的时候,大部分都是使用脚本文件startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令来启动Tomcat的.你们必定知道改如何使用它,可是它们到底是如何实现的,下面就一点一点的分析。app
因为在生产环境中,Tomcat通常部署在Linux系统下,因此将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与中止进行分析。dom
Linux下启动Tomcat的命令:socket
sh startup.sh
下面将从shell脚本startup.sh开始分析Tomcat的启动过程。ide
startup.sh脚本代码清单:oop
# Better OS/400 detection: see Bugzilla 31132 os400=false case "`uname`" in OS400*) os400=true;; esac # 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" fi done 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 else 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 fi fi exec "$PRGDIR"/"$EXECUTABLE" start "$@"
从代码清单能够看出有两个主要的变量,分别是:
1. PRGDIR:当前shell脚本所在的路径; 2. EXECUTABLE:脚本catalina.sh。
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
咱们知道执行了shell脚本catalina.sh,而且传递参数start。
catalina.sh 脚本代码(部分)清单:
shift touch "$CATALINA_OUT" if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift eval $_NOHUP "\"$_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 $_NOHUP "\"$_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 "&" fi if [ ! -z "$CATALINA_PID" ]; then echo $! > "$CATALINA_PID" fi echo "Tomcat started."
从上面能够看出,脚本最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。Bootstrap的main方法的实现以下:
public static void main(String args[]) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { 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 { 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")) { //传递参数为start daemon.setAwait(true); daemon.load(args); daemon.start(); } 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); } }
当传递参数start的时候,command等于start,此时main方法的执行步骤以下:
初始化Bootstrap
public void init() throws Exception{ // Set Catalina path setCatalinaHome(); //1.设置Catalina路径,默认为Tomcat的根目录 setCatalinaBase(); initClassLoaders();//2.初始化Tomcat的类加载器 Thread.currentThread().setContextClassLoader(catalinaLoader);//3.并设置线程上下文类加载器 SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); //4.用反射实例化org.apache.catalina.startup.Catalina对象,而且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外) 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方法:
/** * 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);//用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件。 if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); }
备注:如何加载和解析server.xml配置文件,后面会博客会陆续给出。
启动Tomcat
当传递参数start的时候,调用Bootstrap的load方法以后会接着调用start方法:
/** * Start the Catalina daemon. */ public void start() throws Exception { if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);//启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法 method.invoke(catalinaDaemon, (Object [])null); }
Catalina的start方法以下:
/** * Start a new server instance. */ public void start() { //1.验证Server容器是否已经实例化 if (getServer() == null) { load(); //若是没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只容许Server容器经过配置在server.xml的方式生成,用户也能够本身实现Server接口建立自定义的Server容器以取代默认的StandardServer。 } 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(); //2.启动Server容器 } 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()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook();//3.设置关闭钩子 } 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); } } if (await) { await();//4.最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令 stop();//5.若是Tomcat运行正常且没有收到shutdown命令,是不会向下执行此方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,而后顺序执行stop方法中止Tomcat } }
Catalina的await方法实际只是代理执行了Server容器的await方法。
/** * Await and shutdown. */ public void await() { getServer().await(); }
以Server的默认实现StandardServer为例,其await方法以下:
@Override public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { try { awaitThread = Thread.currentThread(); while(!stopAwait) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { // continue and check the flag } } } finally { awaitThread = null; } return; } // Set up a server socket to wait on try { awaitSocket = new ServerSocket(port, 1, InetAddress.getByName(address));//建立socket链接的服务端对象ServerSocket } catch (IOException e) { log.error("StandardServer.await: create[" + address + ":" + port + "]: ", e); return; } try { awaitThread = Thread.currentThread(); // Loop waiting for a connection and a valid command while (!stopAwait) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; } // Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder();//建立一个对象循环接收socket中的字符 try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. log.warn(sm.getString("standardServer.accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste); continue; } catch (AccessControlException ace) { log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { if (stopAwait) { // Wait was aborted with socket.close() break; } log.error("StandardServer.await: accept: ", e); break; } // Read a set of characters from the socket 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--; } } finally { // Close the socket now that we are done with it try { if (socket != null) { socket.close(); } } catch (IOException e) { // Ignore } } // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { //若是接收到的命令与SHUTDOWN匹配(因为使用了equals,因此shutdown命令必须是大写的),那么退出循环等待 log.info(sm.getString("standardServer.shutdownViaPort")); break; } else log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received"); } } finally { ServerSocket serverSocket = awaitSocket; awaitThread = null; awaitSocket = null; // Close the server socket and return if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { // Ignore } } } }
至此,Tomcat启动完毕。
备注:如何启动server,这里不作过多解释,后面会有专门的博客介绍《容器启动过程分析》。