springboot应用启动流程分析,嵌入式tomcat

之前我们分析了下springboot自动装载的原理,现在我们看看springboot应用启动的流程:
一般调用如下:

// 应用代码

 SpringApplication.run(MiddlewareApplication.class, args);
// SpringApplication.java
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	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();
	}
	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;
	}


public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE;

	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

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

	static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
		if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.SERVLET;
		}
		if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.REACTIVE;
		}
		return WebApplicationType.NONE;
	}

	private static boolean isAssignable(String target, Class<?> type) {
		try {
			return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
		}
		catch (Throwable ex) {
			return false;
		}
	}
}

可以看到,这里首先会判断当前ApplicaitonContext的类型,判断的依据是判断当前环境下是否存在对应的类,返回相应的WebApplicationType,获取到响应WebApplicationType之后会初始化对应的ApplicationContext:

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

默认情况下,如果没有web相关类的话,初始化的是:
org.springframework.context.annotation.AnnotationConfigApplicationContext,这个我们在讲解springboot自动装配的时候有过说明。
如果是web的话,比如我们默认使用的基于Servlet:
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
		implements AnnotationConfigRegistry {
	private final AnnotatedBeanDefinitionReader reader;
	private final ClassPathBeanDefinitionScanner scanner;
	private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
	private String[] basePackages;

	public AnnotationConfigServletWebServerApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	...
}

可以看到,这里还是和AnnotationConfigApplicationContext一样,使用了AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner.

我们看下,大家有时候在基于springboot构建web应用的时候,只要导入相应的jar包,就可以开启web相关应用,我们看下实现逻辑:
在这里插入图片描述
在其父类ServletWebServerApplicationContext我们发现如下方法:

// ServletWebServerApplicationContext.java
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		registerWebApplicationScopes();
	}
	@Override
	public final void refresh() throws BeansException, IllegalStateException {
		try {
			super.refresh();
		}
		catch (RuntimeException ex) {
			stopAndReleaseWebServer();
			throw ex;
		}
	}

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
	@Override
	protected void finishRefresh() {
		super.finishRefresh();
		WebServer webServer = startWebServer();
		if (webServer != null) {
			publishEvent(new ServletWebServerInitializedEvent(webServer, this));
		}
	}

	@Override
	protected void onClose() {
		super.onClose();
		stopAndReleaseWebServer();
	}
	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();
	}
	protected ServletWebServerFactory getWebServerFactory() {
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
		if (beanNames.length == 0) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
					+ "ServletWebServerFactory bean.");
		}
		if (beanNames.length > 1) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
					+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
	}

可以看到在onFresh的时候,执行了createWebServer,在这里创建了webserver,
而这里的ServletWebServerFactory就是创建webserver的关键

在这里插入图片描述
还记之前在研究springboot装载的时候会按照响应条件加载META-INF/spring.factories中相关配置类,其中有一个
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration这个就是装载webserver类的处理:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
...
}
@Configuration
class ServletWebServerFactoryConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedJetty {

		@Bean
		public JettyServletWebServerFactory JettyServletWebServerFactory() {
			return new JettyServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedUndertow {

		@Bean
		public UndertowServletWebServerFactory undertowServletWebServerFactory() {
			return new UndertowServletWebServerFactory();
		}

	}

}

可以看到只要相应webserver对应的类能够存在导入,则会导入对应的web服务类型,我们以tomcat
为例,,如果存在tomcat服务器相关类型后,则使用TomcatServletWebServerFactory

// TomcatServletWebServerFactory.java
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);
	}
	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

// TomcatWebServer.java
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 {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				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
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

这里能够基于注解自动装载tomcat服务器,而不是像一般用的打成war包然后部署到tomcat服务器里面去,tomcat提供了嵌入式tomcat支持,能够像普通写代码一样,在代码里直接生成tomcat web容器。

热门文章

暂无图片
编程学习 ·

php编写的旅游网站

使用PHP编写一个简单的旅游网站! wampserver集成环境编写php+mysql使用最新的Bootstrap(v4.5.0)框架详细的前端功能详细的用户后台管理具体又详细的文件上传函数文件提取链接:https://pan.baidu.com/s/1EeS4o4FnoSWLQ8f_oFg0CQ 提取码:idqm
暂无图片
编程学习 ·

实战系列-Spring Cloud微服务中三把利器Feign、Hystrix、Ribbon

导语在之前的分享中分享过关于Fegin的底层实现原理,以及Spring Cloud OpenFegin的启动原理。在这次的分享中主要总结一下Spring Cloud 微服务架构的三把利器。对于Fegin、Hystrix、Ribbon三个组件来说它们之间是什么样的关系。怎么样综合使用等这些问题就是这次分享的内容文章…
暂无图片
编程学习 ·

UE4中让某个UI位于窗口的最顶端

1.处于同一嵌套层级的UI 可以使用Set ZOrder 设置那个Widget位于屏幕的最前面2.创建一个user widget 叫做ui_umg,里面加上两个按钮3.创建另外一个widget 叫做ui_pic,里面加上一个image4.这步是重点,ui_umg中按钮点击的时候 使用create widget 生成一个ui_pic, 但是这个时…
暂无图片
编程学习 ·

EasyNTS上云网关内配置EasyNVR云终端成功后显示设备离线问题解决

之前为大家简单介绍过EasyNTS上云网关,一部分是为了进行网络穿透而产生的设备,目前有部分用户已经在进行了内测,我们也会收集一些内测用户的问题,集中排查解决,以达到更好的使用效果。有的用户在EasyNTS上云网关内进行流媒体终端EasyNVR硬件配置测试时,设备已经配置完成,…
暂无图片
编程学习 ·

LeetCode题解(0762):二进制表示中质数个计算置位(Python)

题目:原题链接(简单)解法 时间复杂度 空间复杂度 执行用时Ans 1 (Python) O(N)O(N)O(N) O(1)O(1)O(1) 200ms (99.05%)Ans 2 (Python)Ans 3 (Python)LeetCode的Python执行用时随缘,只要时间复杂度没有明显差异,执行用时一般都在同一个量级,仅作参考意义。解法一:【思路】…
暂无图片
编程学习 ·

51小项目——使用proteus搭建简易的光照度计-(1)

总述 本项目基于51单片机,实现了对光敏电阻两端电压信号的简单获取,并通过数码管显示,蜂鸣器可以根据电压信号的大小发出不同间隔的声音。 注意: 由于疫情原因限制,无法返校制作实物,故本项目仅在proteus中完成了仿真,未能完成实物制作,仿真结果可能与实物结果不符 介绍…
暂无图片
编程学习 ·

趣谈:C++中引用和只指针的区别

1.引用必须初始化,不可以为空,不可以null;指针可以位NULL,可以在任何时候初始化. 2.引用一生只爱一次,一生只爱一个人,一旦绑定一个对象,就不能换对象;指针是情场老手,可以随意的更换对象. 3.引用沉溺爱情,丧失了自己,如果使用sizeof(&),返回的是他对象的大小,而指针是他自…
暂无图片
编程学习 ·

寻找凸包(Graham扫描法)

寻找凸包(Graham扫描法)题意描述对任意给定的平面上的点集,求最小凸多边形使得点集中的点要么在凸多边形的边上,要么在凸多边形的内部。Graham算法描述 1、在所有的点中找到一点p0,使得p0的纵坐标值最小,在有多个最小纵坐标的情况下,找横坐标最小的那一个。 2、将所有的…
暂无图片
编程学习 ·

靶机实战(DC-4)

DC-4实验环实验过程信息收集主机发现端口扫描服务发现网站指纹漏洞发现漏洞利用提权总结 实验环 靶机:DC-4 测试机:kali(IP:192.168.186.128),win10 实验过程 信息收集 主机发现 netdiscover -I eth0 -r 192.168.186.0/24 nmap -sn 192.168.186.0/24 arp-scan -l ,arp-sc…
暂无图片
编程学习 ·

Android 8.0 蓝牙遥控器自动配对

本文要实现的是在 android 8.0 的平台上,蓝牙遥控器与TV自动配对,具体就是在TV端打开配对界面,TV端开始搜索远程蓝牙设备,按下遥控器按键让蓝牙遥控器进入对码模式,此时蓝牙遥控器就能作为一个远程蓝牙设备被发现,TV端扫描到这个远程蓝牙设备(蓝牙遥控器),就会自动进行…
暂无图片
编程学习 ·

Linux学习日记 7.1 (用户)

MOOC链接 一。什么是用户和用户组 1.用户 UID (User‘s ID)是识别用户权限的标识,用户登陆系统所处的角色是通过UID来实现的,而非用户名,因此,每个用户的UID必须是唯一的。 Linux中的用户分成三类 ※ 系统管理员用户:拥有整个系统所有的权限,只能有一个,即根用户root,…
暂无图片
编程学习 ·

VS2013编译通过但代码中有红色波浪线

问题描述:VS2013编译无问题但代码中有许多红色下划波浪线解决方案: 1、有些博客分享的方法是:项目属性->c/c++ ->常规->附加包含目录->添加包含代码文件夹的根目录;再填入:$(ProjectDir) 这个方法我没有改成功 2、最后用这个方法解决的:点击上方工具栏的工具…
暂无图片
编程学习 ·

Java网络编程

端口号范围:0~65535,建议选择1024以上 UDP:面向无连接,数据不安全,速度快,不区分客户端和服务器(有发送端和接收端)(发短信) TCP:面向连接(三次握手),数据安全,速度略低,分为客户端和服务器(打电话) 1.UDP package day26;import java.io.IOException; import…
暂无图片
编程学习 ·

布局优化 include viewstub merge 及源码解析

我只是一个无情的搬运工 布局是我们再开发应用时必不可少的工作,通常情况下,布局并不会成为工作中的难点。但是,当你的应用变得越来越富咱,页面越来越多时,布局上的优化工作就成了性能优化的第一步。因为布局上的优化并不像其他优化方式那么复杂,通过Android Sdk提供的Hi…
暂无图片
编程学习 ·

二进制与十进制转换工具类

package util;/*** 二进制工具类* * @author 谢辉* @time 2020.07.01**/ public class BinaryUtil {/*** 十进制数字转二进制* * @param num 十进制数字* @param strResult 结果容器,追加结果用,* @return 返回结果字符串*/public static String DecimalToBinary(Integ…
暂无图片
编程学习 ·

ElasticSearch 索引设置总结

在使用ES时,我们常见的就是需要生成一个template来定义索引的设置,分词器,Mapping.本文将基于项目经验来总结一些常用的配置。Index设置index.refresh_interval 配置一个刷新时间,将index buffer刷新到os cache的时间间隔,刷新到os cache的数据才可以被索引到,默认是1s.如…
暂无图片
编程学习 ·

Android中自定义View的自定义属性

自定义属性 自定义属性 1命名空间 2 1.1. 什么是命名空间 2 1.2. android命名空间 2 1.3. 自定义命名空间 2 配置文件(attrs.xml) 3 2.1. Android的配置文件 3 2.2. 自定义配置文件 4 获取属性 4 3.1. 1.设置好命名空间 4 3.2. 2.设置自定义命名空间自定义属性值 4 3.3. 3.通过…