02-tomcat的启动和关闭流程
本套代码走读基于tomcat7.0的源码,分析之前,先下载tomcat源码,导入ide。可以参考
https://blog.youkuaiyun.com/z583773315/article/details/56353311
我们平时使用tomcat启动都是使用startup.bat或startup.sh脚本,所以要分析启动流程可以先看下这个脚本。以start.bat为类:
set “CURRENT_DIR=%cd%”
if not “%CATALINA_HOME%” == “” goto gotHome
:gotHome
if exist “%CATALINA_HOME%\bin\catalina.bat” goto okHome
set “EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat”
call “%EXECUTABLE%” start %CMD_LINE_ARGS%
只看上面关键几行,即使不懂bat脚本语言,也大致可以看出来这是要执行catalina.bat脚本,所以继续看catalina.bat:
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
以及最后一堆类似如下的语句
%_EXECJAVA% %JAVA_OPTS% … %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
可以看到这是要启动Bootstrap类,同时传入参数start。
一丶tomcat的启动流程
所以可以知道tomcat的入口在Bootstap的main方法,大致流程图如下:
1.Bootstrap.init()
public void init()
throws Exception
{
setCatalinaHome(); //设置catalina.home路径
setCatalinaBase(); //设置catalina.base路径
initClassLoaders(); //初始化classloader
Thread.currentThread().setContextClassLoader(catalinaLoader); //设置当前线程的classloader为catalinaLoader。
SecurityClassLoad.securityClassLoad(catalinaLoader); //从catalinaLoader中加载必要的class
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
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); //设置catalina的parentClassLoader为sharedLoader
catalinaDaemon = startupInstance; //把catalina对象赋值给成员catalinaDaemon。
}
init方法主要是做一些初始化工作,确保catalina.home,catalina.base目录存在,初始化自定义类加载器,加载必要的class文件等等。具体类加载器初始化过程查看03-tomcat的类加载器
2.Bootstrap.load() ==> Catalina.load()。删除了部分不重要的代码。
public void load() {
initDirs(); //还是在确保catalina.home和base的路径
initNaming();
Digester digester = createStartDigester(); //初始化digester对象用于解析server.xml
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
file = configFile(); //创建server.xml的文件对象,默认在catalina.base下的conf/server.xml路径
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
}
try {
inputSource.setByteStream(inputStream);
digester.push(this); //把catalina对象压到digester的栈对象stack里。
digester.parse(inputSource); //解析server.xml,并创建xml中的server。service等对象
} catch (SAXParseException spe) {
return;
} catch (Exception e) {
return;
} finally {
try {
inputStream.close();
} catch (IOException e) {
}
}
getServer().setCatalina(this);//getServer获取的就是digester创建的StandardServer对象
initStreams();
try {
getServer().init();//初始化StandardServer对象以及各子容器
} catch (LifecycleException e) {
}
}
load方法重点就是解析server.xml和初始化容器对象。具体类加载器初始化过程查看04-tomcat的server.xml解析和05-tomcat的容器初始化和启动过程
3.Bootstrap.start() ==> Catalina.start()。删除了不重要的部分。
public void start() {
if (getServer() == null) {
load(); //如果server为空,再次调用上面上面的load()方法。
}
try {
getServer().start(); //启动各个容器
} catch (LifecycleException e) {
return;
}
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);//设置钩子,在tomcat停止的时候做一些清理工作
}
if (await) {
await(); //创建serversocket监听server.xml里面server的8005端口接受tomcat的shutdown请求
stop(); //正常停止,所以移除钩子,然后做一些清理工作
}
}
4.Catalina.await() ==> StandardServer.await()。删除了不重要的部分
public void await() {
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address)); //端口号对应<Server port="8005" shutdown="SHUTDOWN">,创建serversocket阻塞线程,直到监听到8005端口的shutdown请求。
} catch (IOException e) {
return;
}
try {
awaitThread = Thread.currentThread();
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (AccessControlException ace) {
continue;
} catch (IOException e) {
break;
}
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) {
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
} finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) { }
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
break; //接到shutdown请求,退出循环,不再监听端口阻塞线程。从而继续调用上一步里面后续的catalina.stop()停止tomcat。
}
}
} finally {}
}
二丶tomcat的关闭流程
关闭过程和启动类似,查看shutdown.bat里面,可以知道其实是调用Bootstap的main()方法,同时入参为stop。
1.Bootstap.stop() ==> Catalina.stopServer()
public void stopServer(String[] arguments) {
Server s = getServer();
if( s == null ) {
// Create and execute our Digester
Digester digester = createStopDigester();
File file = configFile();
FileInputStream fis = null;
try {
InputSource is =
new InputSource(file.toURI().toURL().toString());
fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is); //和启动一样,先解析server.xml
} catch (Exception e) {
} finally {
}
} else {
try {
s.stop(); //关闭容器
} catch (LifecycleException e) {
}
return;
}
// Stop the existing server
s = getServer();
if (s.getPort()>0) {
Socket socket = null;
OutputStream stream = null;
try {
socket = new Socket(s.getAddress(), s.getPort()); //创建8005socket客户端
stream = socket.getOutputStream();
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i)); //发送shutdown命令到上面启动过程中的serversocket
}
stream.flush();
} catch (ConnectException ce) {
System.exit(1);
} catch (IOException e) {
System.exit(1);
} finally {
}
} else {
System.exit(1);
}
}
ServerSocket接收到shutdown命令后推出循环,停止tomcat。
附tomcat启动参数说明:
这些其实对应的就是catalina.sh里面的那些参数。
我们可以使用这些让web应用独立于tomcat目录之外。