微服務架構Day05-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
@Configuration
public 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 Structure
    2.將嵌入式的Tomcat指定為provided
    3.編寫一個SpringBootServletInitializer的子類,並調用configure方法,傳入SpringBoot應用主程式
    4.啟動伺服器就可以啟動項目
    原理:
  • 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註解,在應用啟動時載入需要的類
    流程:
    1.啟動Tomcat
    2.
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.SpringServletContainerInitializer
3.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應用
Tags: