写点什么

微服务架构框架 SpringBoot 初探:SpringBoot 中 Servlet 容器的自动配置原理

发布于: 2021 年 04 月 16 日
微服务架构框架SpringBoot初探:SpringBoot中Servlet容器的自动配置原理

旧版

配置嵌入式 Servlet 容器

  • SpringBoot 默认使用 Tomcat 作为嵌入式 Servlet 容器

  • 如何定制和修改 Servlet 容器相关配置 1.在配置文件中定制和修改 Servlet 容器有关的配置,本质上是使用 SpringBoot 的默认的嵌入式 Servlet 容器的定制器来修改配置.


通用的servlet容器配置server.xx=
通用的Tomcat配置server.tomcat.xx=
复制代码


2.编写一个嵌入式 Servlet 容器定制器来修改 Servlet 容器的配置在 SpringBoot 中会有==xxxCustomizer==来进行扩展配置,注意学习!!

注册 Servlet 三大组件(Servlet,Filter,Listener)

  • 由于 SpringBoot 默认以 jar 包的方式启动嵌入式 Servlet 容器来启动 SpringBoot 应用,没有 web.xml 文件.

  • 注册三大组件方式:1.ServletRegistrationBean


@Configurationpublic class MyServerConfig {    // 注册Servlet组件    @Bean    public ServletRegistrationBean myServlet(){        ServletRegistrationBean servletRegistrationBean=new ServletRegistrationBean(new MyServlet(),"/my");        return servletRegistrationBean;    }}
复制代码


2.FilterRegistrationBean


  @Bean    public FilterRegistrationBean myFilter(){        FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();        filterRegistrationBean.setFilter(new MyFilter());        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my"));        return filterRegistrationBean;    }
复制代码


3.ServletListenerRegistrationBean


  @Bean    public ServletListenerRegistrationBean myListener(){        ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());        return registrationBean;    }
复制代码


SpringBoot 自动配置 SpringMVC 时,自动注册 SpringMVC 的前端控制器:DispatcherServlet.


    @Configuration    @Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})    @ConditionalOnClass({ServletRegistration.class})    @EnableConfigurationProperties({WebMvcProperties.class})    @Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})    protected static class DispatcherServletRegistrationConfiguration {        private final WebMvcProperties webMvcProperties;        private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfigProvider) { this.webMvcProperties = webMvcProperties; this.multipartConfig = (MultipartConfigElement)multipartConfigProvider.getIfAvailable(); }
@Bean( name = {"dispatcherServletRegistration"} ) @ConditionalOnBean( value = {DispatcherServlet.class}, name = {"dispatcherServlet"} ) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, this.webMvcProperties.getServlet().getPath()); //可以通过修改servletpath来修改SpringMVC前端控制器默认拦截的请求路径 registration.setName("dispatcherServlet"); registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); }
return registration; } }
复制代码


  • " / ":表示拦截所有请求,包括静态资源,但是不拦截 jsp 请求," /* ":表示拦截所有请求,包括 jsp 请求


  • 注入 Bean 的几种方式:

  • @Bean 注解

  • 包扫描:

  • @Controller

  • @Service

  • @Repository

  • @Component

  • @Import:

  • 实现 ImportSelector 接口的类

  • 实现 ImportBeanDefinitionRegistry 接口

  • 实现 FactoryBean

SpringBoot 支持其它的 Servlet 容器

  • 默认支持:Tomcat(默认),Jetty,Undertow

  • Tomcat 是最稳定的服务器,一般情况下推荐使用

  • Jetty 更适合长连接的服务,但是长连接的服务 Netty 比 Jetty 更优秀

  • Undertow 更适合于 IO 密集型服务器或者文件服务器,比 Tomcat 优秀

  • Jetty(长连接):


      <dependency>                    <artifactId>spring-boot-starter-jetty</artifactId>                    <groupId>org.springframework.boot</groupId>        </dependency>
复制代码


  • Undertow(不支持 jsp):


    <dependency>            <artifactId>spring-boot-starter-undertow</artifactId>            <groupId>org.springframework.boot</groupId>        </dependency>
复制代码

嵌入式 Servlet 容器自动配置原理

  • EmbeddedWebServerFactoryCustomizerAutoConfiguration: 嵌入式容器的自动配置


@Configuration@ConditionalOnWebApplication@EnableConfigurationProperties({ServerProperties.class})public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {    }
@Configuration @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class}) // 判断当前是否引入了Tomcat依赖 public static class TomcatWebServerFactoryCustomizerConfiguration { public TomcatWebServerFactoryCustomizerConfiguration() { }
复制代码


  • 以 TomcatWebServerFactoryCustomizer 为例:


public void customize(ConfigurableTomcatWebServerFactory factory) {        ServerProperties properties = this.serverProperties;        Tomcat tomcatProperties = properties.getTomcat();        PropertyMapper propertyMapper = PropertyMapper.get();        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds).as(Long::intValue).to(factory::setBackgroundProcessorDelay);        this.customizeRemoteIpValve(factory);        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive).to((maxThreads) -> {            this.customizeMaxThreads(factory, tomcatProperties.getMaxThreads());        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive).to((minSpareThreads) -> {            this.customizeMinThreads(factory, minSpareThreads);        });        propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes).when(this::isPositive).to((maxHttpHeaderSize) -> {            this.customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes).to((maxSwallowSize) -> {            this.customizeMaxSwallowSize(factory, maxSwallowSize);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes).when((maxHttpPostSize) -> {            return maxHttpPostSize != 0;        }).to((maxHttpPostSize) -> {            this.customizeMaxHttpPostSize(factory, maxHttpPostSize);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getAccesslog).when(Accesslog::isEnabled).to((enabled) -> {            this.customizeAccessLog(factory);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);        properties.getClass();        propertyMapper.from(properties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {            this.customizeConnectionTimeout(factory, connectionTimeout);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> {            this.customizeMaxConnections(factory, maxConnections);        });        tomcatProperties.getClass();        propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> {            this.customizeAcceptCount(factory, acceptCount);        });        this.customizeStaticResources(factory);        this.customizeErrorReportValve(properties.getError(), factory);    }
复制代码


  • 对嵌入式配置容器的修改是如何生效的:1.ServerProperties,也是 Servlet 容器定制器 2.嵌入式 Servlet 容器定制器来修改原理:BeanPostProcessorsRegistrar:给容器导入组件 BeanPostProcessor:后置处理器,在 bean 初始化(创建完对象,还没有赋值)时执行初始化工作步骤:1.SpringBoot 根据导入的依赖情况,给容器中添加相应的嵌入式容器工厂 2.容器中某个组件要创建对象时,便会调用后置处理器,只要是嵌入式 Servlet 容器工厂,后置处理器就会工作.3.后置处理器从容器中获取所有嵌入式容器处理器定制器,调用嵌入式容器处理器定制器中的方法对嵌入式容器处理器进行配置

嵌入式 Servlet 容器启动原理

1.SpringBoot 应用启动 run 方法 2.SpringBoot 刷新 IOC 容器 refreshContext(创建 IOC 容器对象,并初始化容器,创建容器中的每一个组件.如果是 web 应用就创建 AnnotationConfigEmbeddedWebApplicationContext,否则创建默认的 AnnotationConfigApplicationContext)3.刷新创建好的容器 refresh(context)4.此时调用重写的 onRefresh()方法 5.web 中 IOC 容器会创建嵌入式的 Servlet 容器:createEmbeddedServletContainer()6.获取嵌入式的 Servlet 容器工厂,从 IOC 容器中获取嵌入式 Servlet 容器工厂组件,当该组件存在时,Tomcat 嵌入式 Servlet 容器工厂创建对象,后置处理器就获取所有定制器来定制 Tomcat 嵌入式 Servlet 容器的配置 7.使用 Tomcat 嵌入式 Servlet 容器工厂获取嵌入式 servlet 容器 8.嵌入式的 Servlet 容器创建对象并启动 Servlet 容器,先启动嵌入式的 Servlet 容器,再将 IOC 容器中对象获取出来至此,完成 IOC 容器启动创建嵌入式 Servlet 容器

使用外置的 Servlet 容器

嵌入式 Servlet 容器:


  • 优点:简单,便捷

  • 缺点:默认不支持 jsp,优化定制复杂(使用定制器[ ServerProperties,自定义定制器],自己创建嵌入式 Servlet 容器的创建工厂)

  • 外置的 Servlet 容器:外置安装 Tomcat-应用 war 包的方式打包步骤 1.创建一个 war 项目,配置好项目的 Project Structure2.将嵌入式的 Tomcat 指定为 provided3.编写一个 SpringBootServletInitializer 的子类,并调用 configure 方法,传入 SpringBoot 应用主程序 4.启动服务器就可以启动项目<font color=red>原理:</font>

  • jar 包:执行 SpringBoot 主类的 main 方法,启动 IOC 容器,创建嵌入式 Servlet 容器

  • war 包:启动服务器,服务器启动 SpringBoot 应用(SpringBootServletInitializer),然后才能启动 IOC 容器

  • servlet3.0 的 8.2.4 Shared libraries / runtimes pluggability 中的规则:1.服务器启动(web 应用启动)会创建当前 web 应用里面每一个 jar 包里面 ServletContainerInitializer 实例 2.ServletContainerInitializer 的实现放在 jar 包的 META-INF/services 文件夹下,有一个 javax.servlet.ServletContainerInitializer 的文件,内容是 ServletContainerInitializer 实现类的全类名 3.可以使用 @HandleTypes 注解,在应用启动时加载需要的类<font color=red>流程:</font>1.启动 Tomcat2.


org\springframework\spring-web\5.1.9.RELEASE\spring-web-5.1.9.RELEASE.jar\META-INF\services\javax.servlet.ServletContainerInitializer
复制代码


Spring 的 web 模块中有这个文件:org.springframework.web.SpringServletContainerInitializer3.SpringServletContainerInitializer 将 @HandleTypes({WebApplicationInitializer.class})标注的所有类型的类都传入到 onStartup 方法的 Set<Class<?>>中,为 WebApplicationInitializer 类型的类创建实例 4.每一个 WebApplicationInitializer 都调用自己的 onStartup 方法启动 5.SpringBootServletInitializer 类会创建对象并执行 onStartup 方法启动 6.SpringBootServletInitializer 执行 onStartup 方法会调用 createRootApplicationContext 创建容器


protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {    // 创建SpringApplicationBuilder构建器        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();        builder.main(this.getClass());        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);        if (parent != null) {            this.logger.info("Root context already created (using as parent).");            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});        }
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); // 调用configure方法,子类重写该方法,将SpringBoot的主程序类传入进来 builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)}); // 使用builder创建一个Spring应用 SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(this.getClass())); }
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } // 启动Spring应用 return this.run(application); }
复制代码


7.Spring 就启动成功,并且创建 IOC 容器


  protected WebApplicationContext run(SpringApplication application) {        return (WebApplicationContext)application.run(new String[0]);    }
复制代码


  • 先启动 Servlet 容器,再启动 SpringBoot 应用

发布于: 2021 年 04 月 16 日阅读数: 12
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
微服务架构框架SpringBoot初探:SpringBoot中Servlet容器的自动配置原理