springboot源码-内置tomcat初始化

前言:
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,则退出线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值