Spring Boot 中的 Tomcat 是如何启动的?

news/2024/7/24 7:24:19 标签: tomcat, 开发语言, 程序人生, java, spring boot

我们知道 Spring Boot 给我们带来了一个全新的开发体验,让我们可以直接把 Web 程序打包成 jar 包直接启动,这得益于 Spring Boot 内置了容器,可以直接启动。

本文将以 Tomcat 为例,来看看 Spring Boot 是如何启动 Tomcat 的,同时也将展开学习下 Tomcat 的源码,了解 Tomcat 的设计。

从 Main 方法说起

用过 Spring Boot 的人都知道,首先要写一个 main 方法来启动:

@SpringBootApplication
public class TomcatdebugApplication {
 public static void main(String[] args) {
 SpringApplication.run(TomcatdebugApplication.class, args);
 }
}

我们直接点击run方法的源码,跟踪下来,发现最终的run方法是调用


ConfigurableApplicationContext方法,源码如下:

Spring Boot 中的 Tomcat 是如何启动的?

其实这个方法我们可以简单的总结下步骤为 > 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件

其实上面这段代码,如果只要分析 Tomcat 内容的话,只需要关注两个内容即可,上下文是如何创建的,上下文是如何刷新的,分别对应的方法就是createApplicationContext() 和refreshContext(context),接下来我们来看看这两个方法做了什么。

protected ConfigurableApplicationContext createApplicationContext() {
		Class<!--?--> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
						ex);
			}
		}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

这里就是根据我们的webApplicationType来判断创建哪种类型的 Servlet,代码中分别对应着 Web 类型(SERVLET),响应式 Web 类型(REACTIVE),非 Web 类型(default),我们建立的是Web类型,所以肯定实例化
DEFAULT_SERVLET_WEB_CONTEXT_CLASS

指定的类,也就是


AnnotationConfigServletWebServerApplicationContext类,我们来用图来说明下这个类的关系。

Spring Boot 中的 Tomcat 是如何启动的?

通过这个类图我们可以知道,这个类继承的是
ServletWebServerApplicationContext,这就是我们真正的主角,而这个类最终是继承了AbstractApplicationContext,了解完创建上下文的情况后,我们再来看看刷新上下文,相关代码如下:

Spring Boot 中的 Tomcat 是如何启动的?

这里还是直接传递调用本类的refresh(context)方法,最后是强转成父类
AbstractApplicationContext调用其refresh()方法,该代码如下:

// 类:AbstractApplicationContext	
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
			prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
		registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
				initMessageSource();
// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.这里的意思就是调用各个子类的onRefresh()
				onRefresh();
// Check for listener beans and register them.
				registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
				finishRefresh();
			}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
				}
// Destroy already created singletons to avoid dangling resources.
				destroyBeans();
// Reset 'active' flag.
				cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
			}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

这里我们看到onRefresh()方法是调用其子类的实现,根据我们上文的分析,我们这里的子类是
ServletWebServerApplicationContext。

Spring Boot 中的 Tomcat 是如何启动的?

到这里,其实庐山真面目已经出来了,createWebServer()就是启动 Web 服务,但是还没有真正启动 Tomcat,既然webServer是通过ServletWebServerFactory来获取的,我们就来看看这个工厂的真面目。

Spring Boot 中的 Tomcat 是如何启动的?

走进 Tomcat 内部

根据上图我们发现,工厂类是一个接口,各个具体服务的实现是由各个子类来实现的,所以我们就去看看
TomcatServletWebServerFactory.getWebServer()的实现。

 @Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
	}

根据上面的代码,我们发现其主要做了两件事情,第一件事就是把 Connnctor (我们称之为连接器)对象添加到 Tomcat 中,第二件事就是configureEngine,这连接器我们勉强能理解(不理解后面会述说),那这个Engine是什么呢?我们查看tomcat.getEngine()的源码:

 
public Engine getEngine() {
 Service service = getServer().findServices()[0];
 if (service.getContainer() != null) {
 return service.getContainer();
 }
 Engine engine = new StandardEngine();
 engine.setName( "Tomcat" );
 engine.setDefaultHost(hostname);
 engine.setRealm(createDefaultRealm());
 service.setContainer(engine);
 return engine;
 }

根据上面的源码,我们发现,原来这个 Engine 是容器,我们继续跟踪源码,找到Container接口

Spring Boot 中的 Tomcat 是如何启动的?

上图中,我们看到了4个子接口,分别是 Engine,Host,Context,Wrapper。我们从继承关系上可以知道他们都是容器,那么他们到底有啥区别呢?我看看他们的注释是怎么说的。

 
/**
 If used, an Engine is always the top level Container in a Catalina
 * hierarchy. Therefore, the implementation's <code>setParent()</code> method
 * should throw <code>IllegalArgumentException</code>.
 *
 * @author Craig R. McClanahan
 */
public interface Engine extends Container {
 //省略代码
}
/**
 * <p>
 * The parent Container attached to a Host is generally an Engine, but may
 * be some other implementation, or may be omitted if it is not necessary.
 * </p><p>
 * The child containers attached to a Host are generally implementations
 * of Context (representing an individual servlet context).
 *
 * @author Craig R. McClanahan
 */
public interface Host extends Container {
//省略代码
 
}
/*** </p><p>
 * The parent Container attached to a Context is generally a Host, but may
 * be some other implementation, or may be omitted if it is not necessary.
 * </p><p>
 * The child containers attached to a Context are generally implementations
 * of Wrapper (representing individual servlet definitions).
 * </p><p>
 *
 * @author Craig R. McClanahan
 */
public interface Context extends Container, ContextBind {
 //省略代码
}
/**</p><p>
 * The parent Container attached to a Wrapper will generally be an
 * implementation of Context, representing the servlet context (and
 * therefore the web application) within which this servlet executes.
 * </p><p>
 * Child Containers are not allowed on Wrapper implementations, so the
 * <code>addChild()</code> method should throw an
 * <code>IllegalArgumentException</code>.
 *
 * @author Craig R. McClanahan
 */
public interface Wrapper extends Container {
 //省略代码
}

上面的注释翻译过来就是,Engine是最高级别的容器,其子容器是Host,Host的子容器是Context,Wrapper是Context的子容器,所以这4个容器的关系就是父子关系,也就是Engine>Host>Context>Wrapper。我们再看看Tomcat类的源码:

Spring Boot 中的 Tomcat 是如何启动的?

阅读Tomcat的getServer()我们可以知道,Tomcat的最顶层是Server,Server 就是Tomcat的实例,一个Tomcat一个Server;通过getEngine()我们可以了解到 Server 下面是 Service,而且是多个,一个 Service 代表我们部署的一个应用,而且我们还可以知道,Engine容器,一个service只有一个;根据父子关系,我们看setHost()源码可以知道,host容器有多个;同理,我们发现addContext()源码下,Context也是多个;addServlet()表明Wrapper容器也是多个,而且这段代码也暗示了,其实Wrapper和Servlet是一层意思。另外我们根据setConnector源码可以知道,连接器(Connector)是设置在service下的,而且是可以设置多个连接器(Connector)。

根据上面分析,我们可以小结下:Tomcat 主要包含了2个核心组件,连接器(Connector)和容器(Container),用图表示如下:

Spring Boot 中的 Tomcat 是如何启动的?

一个Tomcat是一个Server,一个Server下有多个service,也就是我们部署的多个应用,一个应用下有多个连接器(Connector)和一个容器(Container),容器下有多个子容器,关系用图表示如下:

Spring Boot 中的 Tomcat 是如何启动的?

Engine下有多个Host子容器,Host下有多个Context子容器,Context下有多个Wrapper子容器。

总结

Spring Boot 的启动是通过new SpringApplication()实例来启动的,启动过程主要做如下几件事情:> 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件

而启动 Tomcat 就是在第7步中“刷新上下文”;Tomcat 的启动主要是初始化2个核心组件,连接器(Connector)和容器(Container),一个 Tomcat 实例就是一个 Server,一个 Server 包含多个 Service,也就是多个应用程序,每个 Service 包含多个连接器(Connetor)和一个容器(Container),而容器下又有多个子容器,按照父子关系分别为:Engine,Host,Context,Wrapper,其中除了 Engine 外,其余的容器都是可以有多个


http://www.niftyadmin.cn/n/1603415.html

相关文章

6年Python大神总结10大开发技巧,80%的人都不会

今天给大家分享 10 个我平时整理非常实用的 Python 开发小技巧&#xff0c;内容目录如下&#xff1a; 值得一提的是&#xff0c;这 10 个技巧全部收录在我自己写的 《Python黑魔法指南》里 另外要特别注意&#xff1a;光理论是不够的。这里顺便免费送大家一套2020最新python入门…

什么是真正的架构设计?十年Java经验让我总结出了这些,不愧是我

一. 什么是架构和架构本质 在软件行业&#xff0c;对于什么是架构&#xff0c;都有很多的争论&#xff0c;每个人都有自己的理解。 此君说的架构和彼君理解的架构未必是一回事。因此我们在讨论架构之前&#xff0c;我们先讨论架构的概念定义&#xff0c;概念是人认识这个世界的…

如何实现Python单例模式?最牛实战详解

前言 今天在群里讨论时讨论到了单例模式&#xff0c;这应该是大家最熟悉的一种设计模式了。 简单而言&#xff0c;单例模式就是保证某个实例在项目的整个生命周期中只存在一个&#xff0c;在项目的任意位置使用&#xff0c;都是同一个实例。 单例模式虽然简单&#xff0c;但…

架构师一定要看微服务设计的四个原则

微服务的设计原则 AKF原则 业界对于可扩展的系统架构设计有一个朴素的理念,就是&#xff1a;通过加机器就可以解决容量和可用性问题。(如果一台不行那就两台)。(世界上没有什么事是一顿烧烤不能解决的。如果有&#xff0c;那就两顿。&#xff09; 这一理念在“云计算”概念疯狂…

SSM框架-SpringMVC详解

springmvc概述 Springmvc是spring框架的一个模块&#xff0c;spring和springmvc无需中间整合层整合。 Springmvc是一个基于mvc的web框架 表现层的三大任务&#xff1a; URL到controller的映射http请求参数绑定http响应的生成和输出 MVC设计模式 MVC设计模式是一种通用的软件…

springboot核心基础之spring.factories机制

引言 在java spring cloud项目中&#xff0c;我们常常会在子模块中创建公共方法&#xff0c;那么在另外一个子模块中&#xff0c;需要加载配置文件的时候&#xff0c;往往Spring Boot 自动扫描包的时候&#xff0c;只会扫描自己模块下的类。这个是springboot约定俗成的内容。 …

Java关键字:final,static,this,super

1. final 关键字&#xff1a; final 关键字&#xff0c;意思是最终的、不可改变的&#xff0c;初始化之后就不能再次修改 &#xff0c;用来修饰类、方法和变量&#xff0c;具有以下特点&#xff1a;final 修饰的类不能被继承&#xff0c;final类中的所有成员方法都会被隐式的指…

关于win10发布网站的步骤及问题解决方案

关于win10发布网站的步骤及问题解决方案 一、Win10开启IIS 1.进入控制面板 2.点击程序 3.启动或关闭Windows功能 4.Internet Information Services记得选中应用程序开发那的ASP.NET 5.确定后等待安装 二、VS发布网站到IIS 1.打开IIS 右键我的电脑->管理 2.选择服务和应…