SpringBoot嵌入式容器自动配置及启动原理

前言

本文主要通过源码分析的方式,分析了嵌入式web容器的自动配置原理以及启动流程,以默认容器tomcat为例进行分析;涉及到SpringBoot自动装配原理以及Spring IOC容器启动过程,如果读者掌握上述知识理解起来更加容易。

内置Tomcat容器的自动配置原理

在SpringBoot的启动过程中会自动加载各个模块下的META-INF/spring.factories文件中定义的自动配置类,Tomcat的服务的加载也是如此,首先来看Tomcat自动配置类ServletWebServerFactoryAutoConfiguration

@Configuration(proxyBeanMethods = false)
@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 {
    ......

自动配置类引入了一个内部类BeanPostProcessorsRegistrar,它继承自ImportBeanDefinitionRegistrar接口,这个内部类在spring容器初始化时注入了webServerFactoryCustomizerBeanPostProcessor,即web容器定制器的后置处理器,下面我们来看这部分代码

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

    	......
    
		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class,
					WebServerFactoryCustomizerBeanPostProcessor::new);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
		}

		private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name,
				Class<T> beanClass, Supplier<T> instanceSupplier) {
			if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier);
				beanDefinition.setSynthetic(true);
                //向beanFactory注入beanDefinition
				registry.registerBeanDefinition(name, beanDefinition);
			}
		}
}

registerBeanDefinitions方法注入了webServerFactoryCustomizerBeanPostProcessor类,是web容器定制器的后置处理器,它在springbean完成实例化之后执行,下面看一下这个后置处理器做了什么

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    
    ......
    
    @Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}

    ......
    
	@SuppressWarnings("unchecked")
	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    	//获取了所有WebServerFactoryCustomizer并调用它们的定制方法
        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}

    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
			this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
		return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
	}
}

通过上面的代码可以看到获取了所有类型为WebServerFactoryCustomizer的bean并调用customize定制方法来完成对webServerFactory容器的定制逻辑,在这里对web容器的所有定制器进行了统一调用
定制器具体做了什么?我们可以以tomcat为例来看一下

public class TomcatWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
    
    @Override
	public void customize(ConfigurableTomcatWebServerFactory factory) {
		ServerProperties properties = this.serverProperties;
		ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
		PropertyMapper propertyMapper = PropertyMapper.get();
		propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
		propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds)
				.as(Long::intValue).to(factory::setBackgroundProcessorDelay);
		customizeRemoteIpValve(factory);
		ServerProperties.Tomcat.Threads threadProperties = tomcatProperties.getThreads();
		propertyMapper.from(threadProperties::getMax).when(this::isPositive)
				.to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
		propertyMapper.from(threadProperties::getMinSpare).when(this::isPositive)
				.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
		propertyMapper.from(this.serverProperties.getMaxHttpHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
				.when(this::isPositive)
				.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize));
		propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
				.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
		propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
				.when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0)
				.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
		propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled)
				.to((enabled) -> customizeAccessLog(factory));
		propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
		propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull()
				.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
		propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
				.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
		propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
				.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
		propertyMapper.from(tomcatProperties::getProcessorCache)
				.to((processorCache) -> customizeProcessorCache(factory, processorCache));
		propertyMapper.from(tomcatProperties::getKeepAliveTimeout).whenNonNull()
				.to((keepAliveTimeout) -> customizeKeepAliveTimeout(factory, keepAliveTimeout));
		propertyMapper.from(tomcatProperties::getMaxKeepAliveRequests)
				.to((maxKeepAliveRequests) -> customizeMaxKeepAliveRequests(factory, maxKeepAliveRequests));
		propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText()
				.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
		propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText()
				.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
		customizeStaticResources(factory);
		customizeErrorReportValve(properties.getError(), factory);
	}

通过上面的代码知道tomcat定制器完成ServerProperties到tomcat配置的映射,例如编码方式,最大连接数,连接超时时间等配置进行了读取

那么这些定制器是怎么注入到容器中的呢?默认是使用的哪一个呢?我们来看一下WebServerFactoryCustomizer的注入过程

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
//这里启用了自动配置类
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration(proxyBeanMethods = false)
    //因为springboot默认依赖了tomcat的jar,所以ConditionalOnClass注解是生效的
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {

		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	//下面还有Jetty、Undertow、Netty的定制器初始化逻辑,这里省略
	......

由于springboot的jar中默认导入了tomcat的依赖,所以默认注入了TomcatWebServerFactoryCustomizer定制器,而在它的customize方法完成了servlet容器的基本配置
1682320847818.png

:::info
小结:
1.EmbeddedWebServerFactoryCustomizerAutoConfiguration根据依赖情况注入servlet容器的定制器,定制器有Tomcat、Jetty、Undertow、Netty四种
2.ServletWebServerFactoryAutoConfiguration向容器中注入了WebServerFactoryCustomizerBeanPostProcessor后置处理器,他来获取所有的定制器并对servlet容器进行定制
3.如果我们希望使用其他的web容器例如jetty,那么需要在springboot中排除默认的tomcat并引入jetty相关的依赖,根据自动配置原理,springboot会自动完成对jetty容器的定制
:::

嵌入式Tomcat容器的启动流程

通过前半部分的分析,嵌入式容器在启动后会进行自动配置,那么容器在什么时候启动的呢,可以猜到是在项目启动过程中

@SpringBootApplication
public class PandaAdminApplication {

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

}

跟着启动类run方法一直进入SpringApplication#run方法,这个方法获取了容器对象并执行了refreshContext()方法,而这个方法最终调用的是AbstractApplicationContext#refresh(),进行容器初始化

public ConfigurableApplicationContext run(String... args) {

        .....
		ConfigurableApplicationContext context = null;
		.....
		try {
			......
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			......
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

createApplicationContext()获取的对象是ServletWebServerApplicationContext,它继承了ServletWebServerApplicationContext,这就是我们的web应用的默认容器对象,下面是简化版的继承关系图,从图上可以看到这个类顶层接口是BeanFactory
image.png

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

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

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// 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();
			}
        	......
		}
	}

refresh方法是spring ioc容器的初始化的核心类,方法中解析我们配置的bean,生成BeanDefinition并注册到beanFactory中,并在执行完BeanFactoryPostProcessors后置处理、注册事件广播、国际化等等一系列复杂的初始化逻辑后,回调钩子方法onRefresh(),这个时候bean还没有实例化,我们可以对tomcat、servlet、filter的BeanDefinition进行定制

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

private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
            //创建servlet容器
			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
			//TomcatServletWebServerFactory
            ServletWebServerFactory factory = getWebServerFactory();
			createWebServer.tag("factory", factory.getClass().toString());
			this.webServer = factory.getWebServer(getSelfInitializer());
			createWebServer.end();
			...
		}
        //下面时外置web容器的启动流程
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
}


getWebServerFactory()方法从spring容器中获取了web容器,我们看一下这个方法的代码,从容器中获取类型是ServletWebServerFactory的bean,并且判断了web容器只能存在一个,断点看到获取的是TomcatServletWebServerFactory,可以知道默认返回的web容器是tomcat的

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

那为什么默认是获取的tomcat的web容器呢?springboot的自动装配类ServletWebServerFactoryAutoConfiguration给了我们答案:

@Configuration(proxyBeanMethods = false)
@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 {

通过@Import注解引入了web容器配置类,这个类的三个内部类即使web容器工厂,我们看一下ServletWebServerFactoryConfiguration这个类

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

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

		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
			.....
		}

	}

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

		@Bean
		JettyServletWebServerFactory JettyServletWebServerFactory(
			.....
		}

	}

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

		@Bean
		UndertowServletWebServerFactory undertowServletWebServerFactory(
			.....
		}
    	.....

	}

}

我们重点注意ConditionalOnClass这个注解,他的作用是在我们引入相关依赖,比如引入了tomcat相关依赖时,条件便会成立,会向容器中注入TomcatServletWebServerFactory,而之前也讲过,springboot正是默认引入tomcat依赖的
image.pngServletWebServerFactory有三个实现类,通过上面的分析我们知道默认注入的是TomcatServletWebServerFactory

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

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    initialize();
}

private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
    try {
        ......
        // Start the server to trigger initialization listeners
        this.tomcat.start();

        ......
    }
    catch (Exception ex) {
        stopSilently();
        destroySilently();
        throw new WebServerException("Unable to start embedded Tomcat", ex);
    }
}
}

可以看到在这里调用的tomcat.start()启动了tomcat容器,至此web容器便开始启动了。
:::info
小结:
1.SpringBoot应用启动运行run方法,最终会调用到AbstractApplicationContext#refresh方法,创建并初始化我们的IOC容器。
2.在refresh会调用钩子方法onRefresh,web容器重写了这个方法,来创建并初始化嵌入式Servlet容器。
3.通过分析springboot自动装配过程,我们知道默认启动的web容器类是TomcatWebServer,ServerProperties是对应的配置类
:::
参考:
SpringBoot内置tomcat启动原理_springboot启动tomcat原理_yygr的博客-CSDN博客
【springboot】自动整合Tomcat原理_springboot集成tomcat_morris131的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot启动原理主要是基于Spring框架自动配置和约定大于配置的原则,通过SpringApplication类自动配置和加载应用程序的相关组件,然后启动应用程序。 具体来说,Spring Boot启动过程包括以下几个步骤: 1. 加载应用程序的主配置Spring Boot启动时会加载应用程序的主配置类,也就是带有@SpringBootApplication注解的类。该注解包含了多个注解,包括@ComponentScan、@EnableAutoConfiguration和@Configuration等。 @ComponentScan用于扫描应用程序中的组件,包括@Service、@Component、@Controller、@Repository等。 @EnableAutoConfiguration用于自动配置Spring应用程序所需的各个组件,包括数据源、JPA、Web、日志等。 @Configuration用于声明应用程序的配置类。 2. 执行自动配置 Spring Boot通过@EnableAutoConfiguration注解来自动配置应用程序所需的各个组件。它会根据应用程序的依赖关系和配置信息,自动配置各个组件的实现类,并将它们注册到Spring容器中。 3. 启动应用程序 完成自动配置后,Spring Boot会创建一个Spring应用程序上下文,并加载所有的组件。然后,它会使用EmbeddedServletContainerFactory创建一个嵌入式Servlet容器,并启动应用程序。 4. 运行应用程序 应用程序启动后,Spring Boot会根据应用程序的配置信息,自动读取应用程序的配置文件,包括application.properties和application.yml等。它还会根据配置信息,自动装配各个组件,以便应用程序可以正常运行。 总之,Spring Boot启动原理是基于自动配置和约定大于配置的原则,通过@SpringBootApplication注解、@EnableAutoConfiguration注解、自动配置嵌入式Servlet容器等机制,实现了快速、简便、高效的应用程序开发和部署。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值