SpringBoot 最显著的特点之一,就是 web 项目不用打成 war 包,放在自己安装的 tomcat 中运行,而是直接打成 jar 包,直接用 java -jar 运行即可。在项目的 jar 包中可以看到有关 tomcat 的包,Spring Boot 自动配置了 tomcat,并且在启动项目时启动容器。
1. 原理&源码
容器工厂自动配置类源码:
容器工厂,顾名思义,就是用来生产容器的类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.web.servlet;
@Configuration
class ServletWebServerFactoryConfiguration {
ServletWebServerFactoryConfiguration() {
}
@Configuration
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedUndertow {
public EmbeddedUndertow() {
}
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
@Configuration
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedJetty {
public EmbeddedJetty() {
}
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory() {
return new JettyServletWebServerFactory();
}
}
@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedTomcat {
public EmbeddedTomcat() {
}
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.web.servlet;
@Configuration
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
public ServletWebServerFactoryAutoConfiguration() {
}
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnClass(
name = {"org.apache.catalina.startup.Tomcat"}
)
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
public BeanPostProcessorsRegistrar() {
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (this.beanFactory != null) {
this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
}
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
}
配置文件中又有三个内部配置类,分别对应 undertow, jetty, tomcat三种容器,通过 @Import 导入,并且利用 @ConditionalOnClass, @ConditionalOnMissingBean 进行条件装配。总的来说,实现自动配置有两个前提条件:
- 引入了对应的依赖,这样才能找到相应的类
- 没有自定义容器工厂
spring-boot-starter-web 中已经默认引入了 tomcat 依赖(并且没有其他几种容器的依赖),这就满足了第一条;而且一般情况下也不会自定义容器工厂,所以容器中默认会注入 tomcatServletWebServerFactory,也就是 tomcat 容器工厂。
@EnableConfigurationProperties({ServerProperties.class})则是将配置文件中相关属性和 ServerProperties 绑定,并且注入了该组件,组件属性的用这个组件的属性进进行填充。所以我们可以通过修改配置文件来配置容器。
程序会将 ApplicationContext 包装成 ServletWebServerApplicationContext,
ServletWebServerApplicationContext 部分源码:
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
//获取容器工厂(即上面三种之一)
ServletWebServerFactory factory = this.getWebServerFactory();
//调用工厂的getWebServer方法
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
TomcatServletWebServerFactory 部分源码:
public WebServer getWebServer(ServletContextInitializer... initializers) {
//创建容器
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//添加连接,设置主机、协议等
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0);
}
所以,ServletWebServerApplicationContext 调用 onfresh,createWebServer 获取容器工厂,并用容器工厂创建容器并返回。而且,TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start(),所以,容器初始化就自动启动了。
2. 切换容器
根据上面的结论,我们只需要导入对应的依赖,并且在pom中将默认的 tomcat 依赖排除即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
3. 配置&定制容器
*3.1. 在配置文件中配置:
自动配置类中,可以看到 @EnableConfigurationProperties({ServerProperties.class}),属性是和 ServerProperties 绑定的,即 server 开头的配置,如:server.port,server.tomcat.**。这种方法最简单直接。
3.2. 直接自定义 ConfigurableServletWebServerFactory
因为上面源码可以看到,创建容器是基于容器工厂的,所以可以直接用 @Bean 注入一个该类组件,创建自己的工厂,并给工厂设置属性。
3.3. 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
Customizer 就是把配置文件的值和 ServletWebServerFactory 进行绑定,所以也可以自己用 @Bean 注入一个 Customizer,自己绑定属性。
网友评论