微服務架構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.FilterRegistrationBeanjava

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

3.ServletListenerRegistrationBeanweb

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

SpringBoot自動配置SpringMVC時,自動註冊SpringMVC的前端控制器:DispatcherServlet.spring

@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容器tomcat

使用外置的Servlet容器

嵌入式Servlet容器:springboot

  • 優勢:簡單,便捷
  • 缺點:默認不支持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容器app

protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext)application.run(new String[0]);
    }
  • 先啓動Servlet容器,再啓動SpringBoot應用
相關文章
相關標籤/搜索