在Spring Boot中,默认支持的web容器有 Tomcat, Jetty, 和 Undertow
那么这些web容器是怎么注入的呢?我们一起来分析一下
当SpringBoot应用启动发现当前是Web应用,它会创建一个web版的ioc容器ServletWebServerApplicationContext
这个类下面有一个createWebServer()方法,当执行关键代码ServletWebServerFactory factory = this.getWebServerFactory();时,它会在系统启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> 用于生产Servlet 的web服务器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = this.getServletContext(); if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); // 获取ServletWebFactory ServletWebServerFactory factory = this.getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); // 这里会去调用系统中获取到的web容器工厂类的getWebServer()方法 this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); createWebServer.end(); this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletException var5) { throw new ApplicationContextException("Cannot initialize servlet context", var5); } } this.initPropertySources(); } |
获取ServletWebFactory
1 2 3 4 5 6 7 8 9 10 |
protected ServletWebServerFactory getWebServerFactory() { String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new MissingWebServerFactoryBeanException(this.getClass(), ServletWebServerFactory.class, WebApplicationType.SERVLET); } else if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } else { return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } } |
SpringBoot底层默认有很多的WebServer工厂:TomcatServletWebServerFactory,,JettyServletWebServerFactory和 UndertowServletWebServerFactory
那么究竟返回哪一个工厂呢?
我们需要分析一下底层的自动配置类,ServletWebServerFactoryAutoConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 |
@AutoConfiguration @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() { } ... |
它引入了一个配置类ServletWebServerFactoryConfiguration,这个类里面会根据动态判断系统中到底导入了那个Web服务器的包,然后去创建对应的web容器工厂,spring-boot-starter-web这个依赖默认导入tomcat,所以我们系统会创建TomcatServletWebServerFactory,由这个工厂创建tomcat容器并启动
一旦我们获取到web Server的工厂类,createWebServer()方法就会去调用this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
根据断点一直深入,我们可以发现,Tomcat, Jetty, 和 Undertow的工厂类最后都会去调用getWebServer()方法,设置了链接参数,例如TomcatServletWebServerFactory的getWebServer()方法
在方法的最后,它会执行return this.getTomcatWebServer(tomcat);,跟着断点深入,我们发现它会去调用对应web容器类的构造方法,如TomcatWebServer的构造方法,启动tomcat容器
1 2 3 4 5 6 7 8 9 10 |
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { this.monitor = new Object(); this.serviceConnectors = new HashMap(); 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---会调用this.tomcat.start();启动容器 this.initialize(); } |
Spring Boot默认使用的是tomcat容器,那如果我们想要使用Undertow应该如何切换呢
只需要修改pom文件即可,排除web启动器中tomcat相关的依赖
然后导入Undertow相关启动器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> |
如果想要自己定义一个Servlet容器,可以通过哪些途径呢?
代码样例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.decade.config; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyConfig { @Bean public ConfigurableServletWebServerFactory defineWebServletFactory() { final TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory(); tomcatServletWebServerFactory.setPort(8081); return tomcatServletWebServerFactory; } } |
自定义一个ServletWebServerFactoryCustomizer类,它的下面有一个customize()方法,能把配置文件的值和ServletWebServerFactory 进行绑定
Spring官网提供的样例如下
Spring中有很多xxxxxCustomizer,它的作用是定制化器,可以改变xxxx的默认规则
结合之前的原理分析过程可知,我们分析一个组件的过程可以概括为:
导入对应启动器xxx-starter---->分析xxxAutoConfiguration---->导入xxx组件---->绑定xxxProperties配置类----->绑定配置项
那么如果我们要定制化组件,例如自定义参数解析器或者应用启动端口等,可以怎么做呢?
注意:@EnableWebMvc + 实现WebMvcConfigurer接口:配置类中定义的@Bean可以全面接管SpringMVC,所有规则全部自己重新配置
原理:
DelegatingWebMvcConfiguration类的作用是:只保证SpringMVC最基本的使用
WebMvcConfigurationSupport自动配置了一些非常底层的组件,例如RequestMappingHandlerMapping,这些组件依赖的其他组件都是从容器中获取的,例如ContentNegotiationManager等
由代码可知,WebMvcAutoConfiguration里面的配置要能生效必须系统中不存在WebMvcConfigurationSupport类,所以,一旦配置类上加了@EnableWebMvc,就会导致WebMvcAutoConfiguration没有生效