tomcat在springboot的创建点是和启动点如下图
spring启动后把容器包装成servletinitial 然后在servletcontext启动好后注入进去 这样spring就和tomcat融合 tomcat启动时候会创建servletcontext给Spring的ioc待着
如上图,我们是在onReFresh创建内置tomcat,在finishRefresh启动tomcat
onRefresh:初始化其他的特殊的子容器,我们这边就是ServletWebServerApplicationContext
tomcat的编码有点意思 我们获取任何一个容器的时候,在他内部都会包含获取其父容器的方法
创建tomcat
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
如果webServer 和servletContext 都为空
if (webServer == null && servletContext == null) {
通过spring去加载ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
创建webServer,这边getSelfInitializer里面就是我们spring容器
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
如果servletContext 不为空,调用ServletContextInitializer数组去启动该容器,即给改servletContext配置servlet,filters,listeners context-params 和attribute
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
把我们创建好的额webServer好servletcontext,更新到ConfigurableWebEnvironment中
前提是本身ConfigurableWebEnvironment就有这两个属性
initPropertySources();
}
创建webServer的过程,initializers就是spring的容器
public WebServer getWebServer(ServletContextInitializer... initializers) {
创建tomcat
Tomcat tomcat = new Tomcat();
创建一个临时目录个给当前webServer,并注册了一个钩子在程序退出的时候删除文件夹
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
设置tomcat的baseDir,baseDir是给临时文件使用的,应该是第一个被调用的方法,如果该方法没被调用
,我们默认调用系统属性system properties - catalina.base, catalina.home或者[user.dir]/tomcat.$PORT
tomcat.setBaseDir(baseDir.getAbsolutePath());
根据protocol(默认是Http11NioProtocol)创建connector,这边的逻辑是先看下是否是http协议,然后再看看是否是AJP协议,如果都不是直接塞入协议的名字,然后下一步根据协议的名字调用class.forName
得到协议类,然后通过org.apache.catalina.STRICT_SERVLET_COMPLIANCE属性设置整个tomcat的编码是ISO_8859_1还是UTF-8
Connector connector = new Connector(this.protocol);
给第一个service添加connector,这其中会初始化server(StandardServer)(server会初始化baseDir,设置初始化监听关闭端口为-1,这样就不会被从端口关闭,创建标准的service(StandardServer))然后绑定两者
addConnector:寻找现有的connector数组,将connector添加进去并把connector和service互相绑定,然后启动该connector
tomcat.getService().addConnector(connector);
设置我们自定义的端口号,添加server,bindOnInit属性,添加我们的协议,添加urlEncoding,设置ssl,compression
customizeConnector(connector);
tomcat本身设置connector
tomcat.setConnector(connector);
关闭自动部署
tomcat.getHost().setAutoDeploy(false);
设置backgroundProcessorDelay机制,如果backgroundProcessorDelay为正值,那么子容器的一些任务会有后台线程帮忙处理,为负值,则由当前容器一并处理。这些任务都是周期性的比如例如重新加载等。
为engine配置上Valve,设置container
configureEngine(tomcat.getEngine());
给这个service添加额外的connector,从这可以看出connector和service的关系是多对一 container和service是1对1
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
准备一个context给engine,这里我们设置TomcatEmbeddedContext,这个是spring自己写的,然后配置好
这个context的一些属性
prepareContext(tomcat.getHost(), initializers);
启动tomcat(即tomcat的容器被启动,但是connector没有启动)
return getTomcatWebServer(tomcat);
}
启动TomcatWebServer,这边主要是启动connector
public void start() throws WebServerException {,
synchronized (this.monitor) {
如果已经启动直接返回
if (this.started) {
return;
}
try {
在上面tomcat启动的时候删除了每个service的connectors被删除,我们等spring完全加载好了,再把connectors加进去(之所以删除是因为,那时候spring还没有完全加载成功),这边加入的时候就启动了
connectors
addPreviouslyRemovedConnectors();
获取一个connector,只启动这个connector
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
寻找TomcatEmbeddedContext然后启动类似于(struts 等老框架的servlet),这些老框架的servlet一般都是用线程上下文加载器去加载,所以我们在这边先把我们的classloader替换到线程上下文,然后加载结束在替换回去
startConnector();
}
检测connectors启动是否失败了,失败了抛出异常
checkThatConnectorsHaveStarted();
this.started = true;
TomcatWebServer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true)
+ " with context path '" + getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new WebServerException("Unable to start embedded Tomcat server",
ex);
}
finally {
解绑TomcatEmbeddedContext于classloder
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
}
}
prepareContext--生成TomcatEmbeddedContext
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
这个name就是我们一般定义的一个项目的访问路径
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
这个path就是我们一般定义的一个项目的访问路径
https://www.cnblogs.com/starhu/p/5599773.html 这个地址有一些配置的信息。appBase 只是告诉tomcat 会把这个目录下的项目部署,docBase指定的项目实际位置
context.setPath(getContextPath());
File docBase = (documentRoot != null ? documentRoot
: createTempDir("tomcat-docbase"));
context.setDocBase(docBase.getAbsolutePath());
添加监听者LifecycleListener包含context和当前发送的event类型
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader(
this.resourceLoader != null ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
修改语言的编码
resetDefaultLocaleMapping(context);
添加其他国家的类似国际化的编码
addLocaleMappings(context);
设置是否使用相对地址重定向
context.setUseRelativeRedirects(false);
设置StandardJarScanFilter JarScanner 和需要跳过的tld
configureTldSkipPatterns(context);
设置WebappLoader
WebappLoader loader = new
WebappLoader(context.getParentClassLoader());
设置loaderclass
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
true代表遵守双亲委派机制
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
添加默认的servlet
addDefaultServlet(context);
}
添加jspServlet,JasperInitializer
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
添加监听者StaticResourceConfigurer
context.addLifecycleListener(new StaticResourceConfigurer(context));
合并所以的initializer
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
添加子容器
host.addChild(context);
设置context的staert,valve,监听者,errorpage,请求格式,session
configureContext(context, initializersToUse);
目前是空的实现
postProcessContext(context);
}
initialize
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
TomcatWebServer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
设置engine的id
addInstanceIdToEngineName();
获取第一个context
Context context = findContext();
添加一个监听者,在context启动的时候剔除connector,防止connector启
动,因为这时候spring还没加载完成,这是connector启动就可以接受请求,
会出现异常
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
启动tomcat中的container
this.tomcat.start();
检测上述启动中是否存在异常,存在就抛出异常
rethrowDeferredStartupExceptions();
try {
将当前context和classloader绑定
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
tomcat的线程都是daemon,所以设置一个non-daemon 去关闭tomcat,如果不设置daemon,那么linux会在我们tomcat中和spring中所有飞daemon退出即杀死进程
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
tomcat关闭,spring注册的钩子会在收到关闭信号时候在关闭spring之后调用stopInternal 设置stopAwait为true 去关闭tomcat,这个不仅仅失去关闭tomcat,如果我们不启动这个线程,那么tomcat的方法执行完成就会退出去,从而spring和tomcat都会执行完成就关闭
public void await() {
当为负值代表是嵌入的
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
还记得我们上文分析的为什么关闭端口是8005 我们改成了-1,因为我们是借助于spring去关闭
if( port==-1 ) {
try {
每隔10秒检测一次状态,如果未true直接退出,从而tomcat就可以关闭了
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
当port是正常的时候,我们启动一个socket去监听这个端口,监听到关闭连接请求就退出该线程从而使得tomcat和spring退出
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
}
}
}
}