SpringBoot(七)——嵌入式容器配置

1. 如何定制和修改Servlet容器的相关配置

SpringBoot 默认使用Tomcat作为嵌入式的容器。

  1. 修改和server相关的配置(相关配置都在ServerProperties 类中)
server.port=8081
server.tomcat.uri-encoding=UTF-8
server.servlet.context-path=/crud

//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
  1. 编写一个EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Configuration //实现WebMvcConfigurer接口可以来扩展SpringMVC的功能 
public class MyMvcConfig implements WebMvcConfigurer {

    //编写WebServerFactoryCustomizer来修改Servlet容器的配置
    @Bean
    public WebServerFactoryCustomizer webServletFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {

            @Override //修改相关配置
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8082);
            }
        };
    }
 }

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

2. 注册Servlet三大组件

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

注册三大组件用以下方式

2.1 Servlet

编写Servlet,继承javax.http.HttpServlet,重载方法

//自定义Servlet
public class MyServlet extends HttpServlet {
    @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 Servlet");
    }
}

注册servlet

@Configuration
public class MyServerConfig {

    //注册自己的servlet
    @Bean
    public ServletRegistrationBean myServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(),  "/myServlet");
        return servletRegistrationBean;
    }
...
}

2.2 Filter

编写Filter,实现javax.servlet.Filter 接口,实现相关方法

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.....");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {

    }
}

注册Filter

@Configuration
    public class MyServerConfig {

        //注册filetr
        @Bean
        public FilterRegistrationBean myFilter() {
            FilterRegistrationBean register = new FilterRegistrationBean(new MyFilter());
            //设置拦截路径
            register.setUrlPatterns(Arrays.asList("/myServlet","/"));
            return register;
        }
        ...
      }

2.3 Listener

自定义Listener,有很多不同的Listener,这里写的是监听容器初始化和销毁的Listener

//自定义监听容器创建销毁的listener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed...");
    }
}

注册listener


    @Configuration
    public class MyServerConfig {
        //注册Listener
        @Bean
        public ServletListenerRegistrationBean myListener() {
            ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());
            return servletListenerRegistrationBean;
        }
        ...
      }

SpringBoot 自动配置SpringMvc的时候,自动的注册SpringMVC的前端控制器DIspatcherServlet, DispatcherServletAutoConfiguration中:

 @Bean(
            name = {"dispatcherServletRegistration"}
        )
        @ConditionalOnBean(
            value = {DispatcherServlet.class},
            name = {"dispatcherServlet"}
        )
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        	//默认拦截的路径是/,拦截所有请求包括静态资源,但是不包括jsp,/*的话,会拦截jsp
        	//可以通过spring.mvc.servlet.path来修改
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

3. 使用其他的嵌入式容器

SpringBoot默认使用的是Tomcat,并且支持Jetty(使用与长连接,比如通话,聊天)、Undertow(不支持jsp)、Netty

在这里插入图片描述

如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如Jetty,Undertow,Netty

使用jetty

<!--引入web模板-->
  <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <exclusions>
           <exclusion>
               <artifactId>spring-boot-starter-tomcat</artifactId>
               <groupId>org.springframework.boot</groupId>
           </exclusion>
       </exclusions>
   </dependency>

   <!--引入其他Web容器-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jetty</artifactId>
   </dependency>

使用Undertow

 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <exclusions>
           <exclusion>
               <artifactId>spring-boot-starter-tomcat</artifactId>
               <groupId>org.springframework.boot</groupId>
           </exclusion>
       </exclusions>
   </dependency>

   <!--引入其他Web容器-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-undertow</artifactId>
   </dependency>

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

查看web容器自动配置类

2.0以下是:EmbeddedServletContainerAutoConfiguration,2.0以下是ServletContainer,2.0以上是WebServer

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
    )
    static class EmbeddedTomcat {
       ...
    }

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

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

嵌入式servlet容器工厂(ServletWebServerFactory 实现类):
在这里插入图片描述
WebServer:嵌入式Servlet容器
在这里插入图片描述
嵌入式容器工厂和容器的对应,是依靠相应的@ConditionalOnClass相应的注解实现的,比如Tomcat
在这里插入图片描述

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);
        }
		
		 //将配置好的Tomcat传入进去,返回一个WebServer;并且启动Tomcat服务器
        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }

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

5. 配置修改原理

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置

在像容器中注册ServletWebServerFactoryAutoConfiguration时,也注册了BeanPostProcessorsRegistrar

在这里插入图片描述
BeanPostProcessorsRegistrar:后置处理器注册器(给容器注入一些组件)

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

        public BeanPostProcessorsRegistrar() {
        }

        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
            }

        }

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

webServerFactoryCustomizerBeanPostProcessor:嵌入式Servlet容器定制器的后置处理器,在bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作

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

        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);
        });
    }

   ...
}

使用配置文件来配置Servlet容器,底层也是用的EmbeddedServletContainerCustomizer嵌入式servlet容器定制器来完成配置的。

总结:

  1. SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory
  2. 容器中某个组件要创建对象就会惊动后置处理器 webServerFactoryCustomizerBeanPostProcessor,只要是嵌入式的是Servlet容器工厂,后置处理器就会工作
  3. 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置

6. 嵌入式Servlet容器启动原理

什么时候创建嵌入式的servlet容器工厂?什么时候获取嵌入式的servlet的容器并启动tomcat?

获取嵌入式的Servlet容器工厂步骤:

  1. SpringBoot应用启动运行run方法
  2. refreshContext(context) SpringBoot刷新IOC容器(创建IOC容器对象,并初始化容器,创建容器中的每一 个组件),根据当前环境创建IOC容器
    在这里插入图片描述

在这里插入图片描述

  1. refresh(context) 刷新刚才创建好的ioc容器,和spring中的一样
 public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
  1. 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);
    }
}

**createWebServer**中,获取嵌入式的web容器工厂并且创建Servle容器对象

在这里插入图片描述
5. 接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发webServerFactoryCustomizerBeanPostProcessor
6. 使用容器工厂获取嵌入式的Servlet容器
7. 嵌入式的Servlet容器创建对象并启动Servlet容器
8. 嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等)

也就是说,IOC容器启动的时候,创建并启动Servlet容器

7. 使用外置的Servlet容器

使用嵌入式的Servlet容器

  • 优点:方便、快捷
  • 缺点:默认不支持JSP,优化定制比较复杂(使用定期器,在配置文件中修改配置,或者使用WebServerFactoryCustomizer,也可以自定义WebServerFactory

7.1 步骤

  1. 将项目的打包方式改为war
  2. 编写一个类继承**SpringBootServletInitializer**,并重写configure方法,调用参数的sources方法,将springboot启动类传过去
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    	//传入SpringBoot应用的主程序
        return application.sources(SpringBoot04WebJspApplication.class);
    }

}

  1. 将嵌入式的tomcat的依赖范围改为provided
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        .....
</dependencies>
  1. 创建web应用的目录结构,webapp目录以及里面的WEB-INF目录和web.xml
    在这里插入图片描述
    添加webapp目录,和WEB-INF/web.xml文件
    在这里插入图片描述
  2. 最后就可以把项目打包成war放到tomcat中了, 在IDEA中可以这样配置
    在这里插入图片描述

在创建SpringBoot项目时,使用Spring Initializr创建,选择打包方式为war,1,2,3步骤会自动配置。

7.2 原理

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包::启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器

  1. Servlet3.0标准规定,应用启动时,扫描所有jar包中META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的类并加载

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

  3. 在spring-web-xxx.jar包中的META-INF/services下有javax.servlet.ServletContainerInitializer这个文件,文件中的类是:

org.springframework.web.SpringServletContainerInitializer

对应的类:

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

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
    ....
    }

  1. SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>,为这些WebApplicationInitializer类型的类创建实例;
  2. 每一个WebApplicationInitializer都调用自己的onStartup方法
  3. WebApplicationInitializer的实现类
    在这里插入图片描述
  4. 我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
  5. SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
 public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
            servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

createRootApplicationContext :创建容器

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		
		//1、创建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方法,自定义的SpringBootServletInitializer子类重写了这个方法,将SpringBoot的主程序类传入了进来
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
       
        //使用builder创建一个Spring应用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            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));
        }

        application.setRegisterShutdownHook(false);
        //调用run方法,启动spring应用
        return this.run(application);
    }
  1. Spring的应用就启动并且创建IOC容器
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

			//刷新IOC容器
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值