软件编程
位置:首页>> 软件编程>> java编程>> SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

作者:Decade0712  发布时间:2023-03-31 09:07:05 

标签:SpringBoot,Servlet,容器,嵌入式,定制化组件

嵌入式Servlet容器

在Spring Boot中,默认支持的web容器有 Tomcat, Jetty, 和 Undertow

1、原理分析

那么这些web容器是怎么注入的呢?我们一起来分析一下

当SpringBoot应用启动发现当前是Web应用,它会创建一个web版的ioc容器ServletWebServerApplicationContext

这个类下面有一个createWebServer()方法,当执行关键代码ServletWebServerFactory factory = this.getWebServerFactory();时,它会在系统启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> 用于生产Servlet 的web服务器)

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

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,,JettyServletWebServerFactoryUndertowServletWebServerFactory

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

那么究竟返回哪一个工厂呢?

我们需要分析一下底层的自动配置类,ServletWebServerFactoryAutoConfiguration

@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容器并启动

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

一旦我们获取到web Server的工厂类,createWebServer()方法就会去调用this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});

根据断点一直深入,我们可以发现,Tomcat, Jetty, 和 Undertow的工厂类最后都会去调用getWebServer()方法,设置了链接参数,例如TomcatServletWebServerFactorygetWebServer()方法

在方法的最后,它会执行return this.getTomcatWebServer(tomcat);,跟着断点深入,我们发现它会去调用对应web容器类的构造方法,如TomcatWebServer的构造方法,启动tomcat容器

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

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

2、Servlet容器切换

Spring Boot默认使用的是tomcat容器,那如果我们想要使用Undertow应该如何切换呢

只需要修改pom文件即可,排除web启动器中tomcat相关的依赖

然后导入Undertow相关启动器

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

3、定制Servlet容器配置

如果想要自己定义一个Servlet容器,可以通过哪些途径呢?

  • 通过分析ServletWebServerFactoryAutoConfiguration绑定了ServerProperties配置类可知,我们想要修改容器的配置,只需要修改配置文件中对应的server.xxx配置项即可

  • 创建一个配置类,通过@Configuration+@Bean的方式,向容器中注入一个ConfigurableServletWebServerFactory类的实现类,ConfigurableServletWebServerFactoryServletWebServerFactory类的子类,提供了很多方法供我们使用

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

代码样例如下

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 进行绑定

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

Spring官网提供的样例如下

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

Spring中有很多xxxxxCustomizer,它的作用是定制化器,可以改变xxxx的默认规则

定制化组件

结合之前的原理分析过程可知,我们分析一个组件的过程可以概括为:

导入对应启动器xxx-starter---->分析xxxAutoConfiguration---->导入xxx组件---->绑定xxxProperties配置类----->绑定配置项

那么如果我们要定制化组件,例如自定义参数解析器或者应用启动端口等,可以怎么做呢?

  • 修改配置文件 server.xxx

  • 参考上面编写一个xxxxxCustomizer类

  • 编写自定义的配置类xxxConfiguration:使用@Configuration + @Bean替换、增加容器中默认组件

  • 如果是Web应用,编写一个配置类实现WebMvcConfigurer接口,重写对应方法即可定制化web功能,或者使用@Bean给容器中再扩展一些组件(这条是最重要的)

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

注意:@EnableWebMvc + 实现WebMvcConfigurer接口:配置类中定义的@Bean可以全面接管SpringMVC,所有规则全部自己重新配置

原理:

  • WebMvcAutoConfiguration类是SpringMVC的自动配置功能类。配置了静态资源、欢迎页&hellip;

  • 一旦使用@EnableWebMvc会,@Import(DelegatingWebMvcConfiguration.class)

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

DelegatingWebMvcConfiguration类的作用是:只保证SpringMVC最基本的使用

  • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport表明它是WebMvcConfigurationSupport的子类

  • 它会把所有系统中的 WebMvcConfigurer的实现类拿过来,所有功能的定制都是这些WebMvcConfigurer的实现类合起来一起生效

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

WebMvcConfigurationSupport自动配置了一些非常底层的组件,例如RequestMappingHandlerMapping,这些组件依赖的其他组件都是从容器中获取的,例如ContentNegotiationManager等

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

由代码可知,WebMvcAutoConfiguration里面的配置要能生效必须系统中不存在WebMvcConfigurationSupport类,所以,一旦配置类上加了@EnableWebMvc,就会导致WebMvcAutoConfiguration没有生效

SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

来源:https://blog.csdn.net/Decade0712/article/details/127025304

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com