前言:
1.承接springboot源码-内置tomcat容器创建,对最后一个步骤getTomcatEmbeddedServletContainer(tomcat)进行详细阐述
2.getTomcatEmbeddedServletContainer(tomcat)主要是执行了容器的初始化,核心方法:TomcatEmbeddedServletContainer-initialize
涉及一些知识点:
(参考原文:https://blog.csdn.net/zhangguixian5/article/details/52317173)
1.Catalina主要包括connector和container两个模块,connector负责接收请求,传递给container,而container负责处理请求
2.service的作用是统一管理connector和container,一个service可以包括多个connector和一个container。而server的作用是,管理所有的service,一个server可以包括多个service。server负责管理所有service的生命周期,这样就管理了所有的connector和container,以及connector和container的所有内部组件。这样就不需要单独对connector和container单独进行开启或关闭了。
3.补充说明一下:2中提到的service管理一个container,这个container是指engine
tomcat容器的核心类为:TomcatEmbeddedServletContainer
tomcat初始化流程
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
//加锁,防止该实例同时被初始化多次,因为后面涉及到全局变量的修改
synchronized (this.monitor) {
try {
//步骤1.容器计数累计一,修改engine名称
addInstanceIdToEngineName();
try {
// Remove service connectors to that protocol binding doesn't happen
// yet
//步骤2.解除Connector绑定,避免后面利用LifecycleBase启动容器时启动Connector
removeServiceConnectors();
// Start the server to trigger initialization listeners
//启动tomcat,稍复杂,该步骤单独拿一节来说
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
//步骤3.将子线程中抛出的异常在主线程中继续抛出
rethrowDeferredStartupExceptions();
Context context = findContext();
try {
//步骤4.将命名的context和类加载器绑定
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
//步骤5.开启非守护线程
//tomcat所有的线程都是守护线程,所以创建一个非守护线程(例:Thread[container-0,5,main])来避免服务到这就shutdown了
//定时每10秒检查一次,tomcat服务是否挂了
startDaemonAwaitThread();
}
catch (Exception ex) {
//发生异常,需要将容器计数减一,因为在外层已经累计加一了
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
步骤1.
private void addInstanceIdToEngineName() {
//containerCounter初始值为-1
int instanceId = containerCounter.incrementAndGet();
//如果大于0,说明实例化操作了不止一次
if (instanceId > 0) {
Engine engine = this.tomcat.getEngine();
//修改engine名称
engine.setName(engine.getName() + "-" + instanceId);
}
}
步骤2.
private void removeServiceConnectors() {
//遍历server下面的service列表,默认这里只有一个service,即StandService
for (Service service : this.tomcat.getServer().findServices()) {
//获取service下的连接器列表,默认这里只有一个Connector,克隆列表
Connector[] connectors = service.findConnectors().clone();
//将克隆得到的连接器列表及service放到本地缓存
this.serviceConnectors.put(service, connectors);
//移除连接器,避免后面执行start操作时初始化连接器,注意虽然这里移除了,但是在后续还会添加到对应service里
for (Connector connector : connectors) {
service.removeConnector(connector);
}
}
}
步骤3.
private void rethrowDeferredStartupExceptions() throws Exception {
//获取虚拟主机下子容器
Container[] children = this.tomcat.getHost().findChildren();
for (Container container : children) {
//判断子容器是否是TomcatEmbeddedContext类型
if (container instanceof TomcatEmbeddedContext) {
//获取容器启动器中的异常信息并抛出
Exception exception = ((TomcatEmbeddedContext) container).getStarter()
.getStartUpException();
if (exception != null) {
throw exception;
}
}
//在this.tomcat.start()步骤中其实执行了tomcat子容器的启动流程,这里对子容器状态判断一下
//如果某个子容器状态为非启动状态,则说明这时状态不正常,抛出不合法状态异常
if (!LifecycleState.STARTED.equals(container.getState())) {
throw new IllegalStateException(container + " failed to start");
}
}
}
获取虚拟机:
public Host getHost() {
//获取tomcat执行引擎,这里获取的是StandardEngine
Engine engine = getEngine();
if (engine.findChildren().length > 0) {
//Host是engin的子容器,所以这里可以强转
return (Host) engine.findChildren()[0];
}
//如果没有则创建
Host host = new StandardHost();
host.setName(hostname);
//将Host加入engine中
getEngine().addChild(host);
return host;
}
步骤4.
public static void bindClassLoader(Object obj, Object token,ClassLoader classLoader) throws NamingException {
//检查token对象是否合法
//在NamingContextListener监听器监听"configure_start"类型事件1.会将token信息放入本地缓存中(以便在这里检查使用) 2.绑定命名context(在下面获取)
if (ContextAccessController.checkSecurityToken(obj, token)) {
//获取命名后的context
Context context = objectBindings.get(obj);
if (context == null) {
throw new NamingException
(sm.getString("contextBindings.unknownContext", obj));
}
//命名后的context与类加载器绑定
clBindings.put(classLoader, context);
//原始context与类加载器绑定
clObjectBindings.put(classLoader, obj);
}
}
步骤5.
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
//轮询检查,启动一个socket服务监听tomcat-shutdown命令
TomcatEmbeddedServletContainer.this.tomcat.getServer().await();
}
};
//为该线程设置类加载器
awaitThread.setContextClassLoader(getClass().getClassLoader());
//设置为非守护线程
awaitThread.setDaemon(false);
awaitThread.start();
}
轮询检查:
/**
* Wait until a proper shutdown command is received, then return.
* This keeps the main thread alive - the thread pool listening for http
* connections is daemon threads.
* 等待接收一个正确的shutdown命令,然后返回. 这个任务保证了监听连接程池中的线程作为守护线程可以安心工作
*/
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));
} 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 {
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;
}
// 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
}
}
// 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");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
上述代码核心就是监听8005端口消息,轮询检查stopAwait属性状态,如果为true,则退出线程