SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?

这篇具有很好参考价值的文章主要介绍了SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.前言

我们知道SpringBoot可以直接把传统的war包打成可执行的jar包,直接启动。这得益于SpringBoot内置了容器可以直接启动。本文将以 Tomcat 为例,来看看 SpringBoot 是如何启动 Tomcat 的。

二.源码分析

一.SpringApplication初始化

public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);

    }

}

调用到最终的run方法我们来看一下

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

这里面首先创建了一个SpringApplication的对象,然后调用了run方法,我们先看在创建对象的死后做了什么

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这里面对于我们有用的是WebApplicationType.deduceFromClasspath()方法,因为他返回了一个叫webApplicationType的东西。这个我们一会能用得到。看一下deduceFromClasspath()的源码

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

其中最主要的方法是ClassUtils.isPresent方法

	public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
		try {
			forName(className, classLoader);
			return true;
		}
		catch (IllegalAccessError err) {
			throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
					className + "]: " + err.getMessage(), err);
		}
		catch (Throwable ex) {
			// Typically ClassNotFoundException or NoClassDefFoundError...
			return false;
		}
	}

可以看到就是利用反射来判断传入的className是否存在,如果不存在返回false.

最后this.webApplicationType= WebApplicationType.SERVLET这个东东。

至于其他忽略的源码,之后会在其他源码分析模块讲解

二.run方法的调用

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

这里面对于我们相关的有两个方法一个是context = createApplicationContext();方法,另一个是refreshContext(context)方法,我们分别分析一下源码。

(1)createApplicationContext()

	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);
	}

还记得我们上一部分返回的this.webApplicationType的值是什么么?是Servlet对吧。

那么返回的contextClass就是对应的DEFAULT_SERVLET_WEB_CONTEXT_CLASS值也就是

public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

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

我们看一下返回回来的ConfigurableApplicationContext 发现他是一个接口。因为我们是AnnotationConfigServletWebServerApplicationContext。是他的实现类,所以我们自然而然进入到AnnotationConfigServletWebServerApplicationContext中

SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?

 这是他的类的关系 可以发现最后继承的是AbstractApplicationContext。至此创建线程上线文完成。回过头我们来看一下refreshContext()方法

(2)refreshContext()方法

我们跟随代码来到

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

这块还是调用的本方法的refresh方法最后强转成AbstractApplicationContext,调用它的refresh()方法 

	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}
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();

				// 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。

调用的是这个ServletWebServerApplicationContext类的onRefresh()方法()

	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

关键代码在于createWebServer();

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

这就是创建WebServer的意思,其中最重要的就是通过getWebServerFactory()方法来获取webserver,我们来看一下。

SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?

 看见了么?这个工厂接口有下面这几种实现类,根据上图我们发现,工厂类是一个接口,各个具体服务的实现是由各个子类来实现的,所以我们就去看看 TomcatServletWebServerFactory.getWebServer()的实现。

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		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);
	}

总结

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

而启动 Tomcat 就是在第 7 步中“刷新上下文”;文章来源地址https://www.toymoban.com/news/detail-452674.html

到了这里,关于SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot 源码分析(四) 内置Tomcat分析

    Tomcat文件的目录结构 启动一个Tomcat服务是执行的bin目录下的脚本程序, startup.bat 和 startup.sh .一个是windows的脚本,一个是Linux下的脚本,同样还可以看到两个停止的脚本 shutdown.bat 和 shutdown.sh . startup.bat 脚本内容 catalina.bat 脚本文件 doStart 方法 最后会执行的程序是 MAINCLASS 变量

    2024年02月08日
    浏览(40)
  • 【JAVA面试】SpringBoot如何启动的Tomcat

    提示:文章先作为初版,等后续时间充足后,补充更深的内容 在Spring Boot应用程序中, 内嵌的Tomcat服务器是通过Spring Boot Starter Web模块提供的功能 来启动的。下面是Spring Boot启动Tomcat的大致过程: 引入Spring Boot Starter Web依赖:在项目的pom.xml文件中添加 Spring Boot Starter Web依赖

    2024年02月04日
    浏览(49)
  • SpringBoot源码分析:SpringBoot启动源码(一)

    SpringBoot启动的源码分为前期初始化,和后期启动两个部分,我们从这两个部分开始介绍。 SpringBoot前期初始化主要由下面三行代码组成。  进入SpringApplication.getSpringFactoriesInstances方法  最终进入SpringFactoriesLoader.loadFactoryNames方法  最后SpringFactoriesLoader.loadSpringFactories会到sprin

    2024年02月07日
    浏览(43)
  • SpringBoot源码解读与原理分析(二十七)嵌入式Tomcat

    当Web应用需要部署运行时,传统的做法是将项目打包成war包,然后部署到外置的Web容器中(如最常用的Tomcat容器)。 SpringBoot的一大重要特性是支持嵌入式Web容器,基于SpringBoot的Web应用仅凭一个单独的jar包即可独立运行。 8.1.1 嵌入式Tomcat与普通Tomcat 嵌入式Tomcat是一种可以嵌

    2024年02月22日
    浏览(49)
  • SpringBoot的启动流程源码分析

    new 一个IOC容器,传入配置好的文件xml,在这个地方打bug 在这个debug的栈帧中,下面几个不用看,直接看到getBean 内容如图所示,name传的就是我们在xml的bean标签的id,这里是instanceA 进入到doGetBean后,因为我是从IOC初始化容器debug进来的所以第一次通过。 Object sharedInstance = getSingl

    2024年01月24日
    浏览(47)
  • 【框架源码】SpringBoot核心源码解读之启动类源码分析

    首先我们要先带着我们的疑问,spring boot是如何启动应用程序?去分析SpringBoot的启动源码。 我们在新建SpringBoot项目时,核心方法就是主类的run方法。 SpringApplication.run(ArchWebApplication.class, args) 我们点击run方法进入到源码中,这块传入的了一个我们当前程序主类的类对象以及主

    2024年02月06日
    浏览(44)
  • SpringBoot3.X源码分析(启动流程)

    1 启动入口 静态辅助类,可用于运行使用默认配置(即我们添加的一系列注解)的指定源的 SpringApplication 。 primarySource - 要载入的主要源,即指定源,这里为传入的 Application.class  Class? :泛型决定了任何类都可以传入 args - 应用程序参数(通常从main方法传递) 返回:正在运

    2024年01月23日
    浏览(41)
  • SpringBoot-Run启动流程(源码分析)—看不懂来揍我

    目录 前言 Run()方法 1、实例化SpringApplication对象 1、加载容器 2、装配初始化器 3、装配监听器  4、加载主类 2、执行Run()方法 1、设置headless 2、启用SpringApplicationListener 3、加载Banner 1、图片Banner 2、文本Banner 4、异常报告类加载 5、准备上下文         6、刷新上下文 7、系统

    2024年02月14日
    浏览(39)
  • SpringBoot内嵌Tomcat启动流程

    Spring MVC 让开发者不用了解 Servlet 细节,专注于 Controller 编写 API 接口。Spring Boot 更是采用约定大于配置的设计思想,通过内嵌 Tomcat 的方式让开发者可以快速构建并部署一个 Web 应用。怎么做到的呢? 早期的开发,一般是基于 Spring 和 Spring MVC 构建我们的应用,然后把项目打

    2024年02月02日
    浏览(42)
  • SpringBoot复习:(2)Tomcat容器是怎么启动的?

    SpringApplication的run方法包含如下代码: 其中调用的refreshContext代码如下: 其中调用的refresh方法片段如下: 其中调用的refresh方法代码如下: 其中调用的super.refresh方法代码如下: 其中调用了onRefresh方法: 进入 ServletWebServerApplicationContext的onRefresh方法: createWebServer代码如下:

    2024年02月15日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包