Tomcat源码分析--启动
共 36217字,需浏览 73分钟
·
2021-03-26 08:37
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
首先找到catalina.sh中的启动脚本:
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$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 "&"
启动类为org.apache.catalina.startup.Bootstrap,在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
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")) {
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);
}
}
初始化Bootstrap
在main方法中会首先初始化Bootstrap,然后解析command,根据不同的command执行不同的逻辑处理,通过catalina.sh启动Tomcat传人的参数是"start",不过看代码中command默认也是start。我们首先看Bootstrap的初始化方法bootstrap.init():
public void init() throws Exception {
//初始化类加载器
initClassLoaders();
//设置TCCL
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
//加载并创建启动类:Catalina
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
//通过反射设置parentClassLoader属性
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属性
catalinaDaemon = startupInstance;
}
初始化动作主要是初始化类加载器,然后加载并实例化启动类(Catalina)。这里初始化的类加载器是common、shared、catalina三个类加载器,没有初始化WebApp的类加载器:
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
6.x以后的版本合并了share、common等目录,可以根据参数server.loader、share.loader等决定是否启动老的结构,以创建sharedClassLoader为例:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//创建shareClassLoader时,name参数是share
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
//如果没有配置share.loader,那么使用直接返回父类加载器
return parent;
......
//创建shareClassLoader的逻辑
}
再回到bootstrap.init()的逻辑,创建好类加载器之后,catalinaDemon已经被赋值为一个Catalina对象。
解析command参数
Bootstrap初始化工作完成后,进入command==start的处理逻辑:
if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
一共就三行代码,首先是setAwait(true),看看Bootstrap.setAwait方法:
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
通过反射调用了catalinaDaemon的setAwait方法,前面提到了,catalinaDaemon在Bootstrap初始化的时候生成,我们这里直接看它的setAwait方法:
protected boolean await = false;
public void setAwait(boolean b) {
await = b;
}
方法很简单,就是设置await属性,这里将其设置为了true。然后再回到Bootstrap处理start指令的流程中:
daemon.setAwait(true);//将Catalina的await属性设置为true
daemon.load(args);//加载初始化Tomcat的核心组件
daemon.start();//解析文件,启动组件
启动组件
daemon.load方法主要加载Tomcat的核心组件,这个部分后面再分析,我们直接看daemon.start()方法是如何启动组件的:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
还是通过反射调用Catalina的start方法(部分代码):
protected boolean useShutdownHook = true;
public void start() {
if (getServer() == null) {
//生成server实例
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
// 启动server
getServer().start();
//注册一个shutdownhOOK
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
//await
await();
stop();
}
}
load()方法会解析server.xml节点,对应初始化很多对象,包括server、engine、host、connector等等。最主要的就是顶层Server节点,对应的StandardServer对象:
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
这部分流程后面再分析。启动server后,首先注册了一个shutdownHook(关于shutdownHook可以参考JAVA之ShutdownHook源码分析),主要应对不是通过socket正常退出的情况。前面通过反射设置了await属性为true,所以进入await()方法:
public void await() {
getServer().await();
}
阻塞主线程
Catalina的await方法又调用了getServer的await方法,server在前面的load阶段已经创建,指定的是org.apache.catalina.core.StandardServer,进入它的await方法:
public void await() {
if( port == -2 ) {
//内嵌的tomcat,直接返回
return;
}
if( port==-1 ) {
//如果==-1,根据stopAwait参数间隔10秒死检查循环
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
//根据port创建一个ServerSocket,如果port小于0,会抛出异常
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} 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();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
//调用accept方法阻塞,等待连接
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;
}
// 从socket中读取数据,shutdown是一个字符串可以在server.xml中配置,默认为SHUTDOWN
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;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
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
}
}
boolean match = command.toString().equals(shutdown);
if (match) {
//如果读取的数据是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 {
//断开socket连接
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
代码逻辑很长,其实主要就干几件事:
如果port==-2,表示是内嵌的tomcat,直接返回
如果port==-1,根据标识死循环阻塞
否则根据port创建一个ServerSocket,并且循环阻塞等待socket连接,直到接收到shutdown指令
shutdown指令和对应的port可以在server.xml中自行配置:
<Server port="8005" shutdown="SHUTDOWN">
......
</Server>
就通过这样的方式阻塞主线程,直到shutdown。await方法返回后,调用Catalina.stop()方法停止服务器。大体流程如下:
————————————————
版权声明:本文为CSDN博主「黄智霖-blog」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/huangzhilin2015/article/details/115048022
锋哥最新SpringCloud分布式电商秒杀课程发布
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈