程序员社区

微服务架构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.启动服务器就可以启动项目
    <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.启动Tomcat

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应用
赞(0) 打赏
未经允许不得转载:IDEA激活码 » 微服务架构Day05-SpringBoot之Servlet

一个分享Java & Python知识的社区