您当前的位置: 首页 >  服务器

white camel

暂无认证

  • 2浏览

    0关注

    442博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

SpringBoot —— 自定义 Servlet, Filter, Listener 以及替换web服务器的方式

white camel 发布时间:2020-04-23 19:50:41 ,浏览量:2

目录
  • 一、如何定制和修改Servlet容器的相关配置
  • 二、注册Servlet三大组件【Servlet、Filter、Listener】
  • 三、替换为其他嵌入式web服务器
  • 四、嵌入式Servlet容器启动原理
  • 五、使用外置的Servlet容器

在这里插入图片描述

一、如何定制和修改Servlet容器的相关配置

跳转到目录

1、修改和server有关的配置
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2、编写一个EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Configuration
public class MyServerConfig {

    // 嵌入式Servlet的配置修改(还可以通过全局配置文件的方式修改)
    @Bean
    public WebServerFactoryCustomizer webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8083);
            }
        };
    }
}

代码方式的配置会覆盖配置文件的配置

二、注册Servlet三大组件【Servlet、Filter、Listener】

跳转到目录

  • 由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
1、Servlet
  • 向容器中添加ServletRegistrationBean

MyServlet

public class MyServlet extends HttpServlet {

    // 处理get请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello MyServlet");
    }
}

向容器中添加ServletRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ServletRegistrationBean myServlet() {
        ServletRegistrationBean register = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        // 可以在这里设置
        register.setLoadOnStartup(1);
        return register;
    }
    ......
2、Filter
  • 向容器中添加FilterRegistrationBean

MyFilter

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter process...");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

向容器中添加FilterRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean register = new FilterRegistrationBean(new MyFilter());
        register.setUrlPatterns(Arrays.asList("/myServlet","/"));
        return register;
    }

    ......
3、Listener
  • 向容器中添加FilterRegistrationBean

MyListener

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MyListener.contextInitialized -- web应用启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("MyListener.contextDestroyed -- web应用关闭");
    }
}

向容器中注入ServletListenerRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ServletListenerRegistrationBean myServletContextListener(){
        return new ServletListenerRegistrationBean(new MyServletContextListener());
    }

    ......
三、替换为其他嵌入式web服务器

跳转到目录

  • SpringBoot默认使用的是Tomcat

在这里插入图片描述 如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如JettyUndertow


    org.springframework.boot
    spring-boot-starter-web
    
        
            spring-boot-starter-tomcat
            org.springframework.boot
        
    



    spring-boot-starter-jetty
    org.springframework.boot


    org.springframework.boot
    spring-boot-starter-web
    
        
            spring-boot-starter-tomcat
            org.springframework.boot
        
    



    spring-boot-starter-undertow
    org.springframework.boot

原理:

查看web容器自动配置类

2.0以下是:EmbeddedServletContainerAutoConfiguration

ServletWebServerFactoryAutoConfiguration:嵌入式的web服务器自动配置

@Configuration(
    proxyBeanMethods = false
)
@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 {

EmbeddedTomcat.class

@Configuration(
    proxyBeanMethods = false
)
//判断当前是否引入了Tomcat依赖;
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
/**
  *判断当前容器没有用户自己定义ServletWebServerFactory:嵌入式的web服务器工厂;
  *作用:创建嵌入式的web服务器
  */
@ConditionalOnMissingBean(
    value = {ServletWebServerFactory.class},
    search = SearchStrategy.CURRENT
)
public static class EmbeddedTomcat {

ServletWebServerFactory:嵌入式的web服务器工厂

@FunctionalInterface
public interface ServletWebServerFactory {
    //获取嵌入式的Servlet容器
    WebServer getWebServer(ServletContextInitializer... initializers);
}

工厂实现类 在这里插入图片描述 WebServer:嵌入式的web服务器实现 在这里插入图片描述TomcatServletWebServerFactory为例,下面是TomcatServletWebServerFactory类

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }

    //创建一个Tomcat
    Tomcat tomcat = new Tomcat();

    //配置Tomcat的基本环境,(tomcat的配置都是从本类获取的,tomcat.setXXX)
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var5 = this.additionalTomcatConnectors.iterator();

    while(var5.hasNext()) {
        Connector additionalConnector = (Connector)var5.next();
        tomcat.getService().addConnector(additionalConnector);
    }

    this.prepareContext(tomcat.getHost(), initializers);

    //将配置好的Tomcat传入进去,返回一个WebServer;并且启动Tomcat服务器
    return this.getTomcatWebServer(tomcat);
}

我们对嵌入式容器的配置修改是怎么生效的?

配置修改原理

ServletWebServerFactoryAutoConfiguration在向容器中添加web容器时还添加了一个组件 在这里插入图片描述 BeanPostProcessorsRegistrar:后置处理器注册器(也是给容器注入一些组件)

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
    private ConfigurableListableBeanFactory beanFactory;

    public BeanPostProcessorsRegistrar() {...}

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {...}

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (this.beanFactory != null) {
            //注册了下面两个组件
            this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
            this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
        }
    }

    private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class beanClass) {...}
}

webServerFactoryCustomizerBeanPostProcessor

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {

    ......

    //在Bean初始化之前
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //判断添加的Bean是不是WebServerFactory类型的
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }
    

关于配置文件是如何设置的,参考EmbeddedWebServerFactoryCustomizerAutoConfiguration类,最后还是使用上面的方便

总结:
  • SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory

  • 容器中某个组件要创建对象就会惊动后置处理器webServerFactoryCustomizerBeanPostProcessor 只要是嵌入式的是Servlet容器工厂,后置处理器就会工作;

  • 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置

四、嵌入式Servlet容器启动原理

跳转到目录 1、SpringBoot应用启动运行run方法 在这里插入图片描述 2、153行,创建IOC容器对象,根据当前环境创建 在这里插入图片描述 3、156行,刷新IOC容器

4、刷新IOC容器中272行,onRefresh();web的ioc容器重写了onRefresh方法,查看ServletWebServerApplicationContext类的onRefresh方法,在方法中调用了this.createWebServer();方法创建web容器

protected void onRefresh() {
    super.onRefresh();

    try {
        this.createWebServer();
    } catch (Throwable var2) {
        throw new ApplicationContextException("Unable to start web server", var2);
    }
}

98行获取嵌入式的web容器工厂 在这里插入图片描述

5、接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发webServerFactoryCustomizerBeanPostProcessor

6、然后99行使用容器工厂获取嵌入式的Servlet容器

7、嵌入式的Servlet容器创建对象并启动Servlet容器;

8、嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等);

使用外置的Servlet容器

五、使用外置的Servlet容器

跳转到目录 1、将项目的打包方式改为war

2、编写一个类继承SpringBootServletInitializer,并重写configure方法,调用参数的sources方法springboot启动类传过去然后返回

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(HelloSpringBootWebApplication.class);
    }
}

3、然后把tomcat的依赖范围改为provided


    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-tomcat
        2.2.1.RELEASE
        provided
    

    ......


4、最后就可以把项目打包成war放到tomcat中了

5、在IDEA中可以这样配置 在这里插入图片描述 6、在创建项目时使用Spring Initializr创建选择打包方式为war,1,2,3步骤会自动配置

如果启动tomcat,报了一大堆错误,不妨把Tomcat改为更高的版本试试,如果你项目中的Filter是继承了HttpFilter,请使用tomcat9版本,9以下好像没有HttpFilter

原理:

1、Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载

2、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

3、在spring-web-xxx.jar包中的METAINF/services下有javax.servlet.ServletContainerInitializer这个文件

文件中的类是:

org.springframework.web.SpringServletContainerInitializer

对应的类:

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set webAppInitializerClasses, ServletContext servletContext) throws ServletException {

        ......

4、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set

关注
打赏
1661428283
查看更多评论
0.0407s