• SpringBoot(七) -- 嵌入式Servlet容器


    一.嵌入式Servlet容器
      在传统的开发中,我们在完成开发后需要将项目打成war包,在外部配置好TomCat容器,而这个TomCat就是Servlet容器.在使用SpringBoot开发时,我们无需再外部配置Servlet容器,使用的是嵌入式的Servlet容器(TomCat).如果我们使用嵌入式的Servlet容器,存在以下问题:
      1.如果我们是在外部安装了TomCat,如果我们想要进行自定义的配置优化,可以在其conf文件夹下修改配置文件来实现.在使用内置Servlet容器时,我们可以使用如下方法来修改Servlet容器的相关配置:
      (1)例如我们可以使用server.port=80来修改我们的启用端口号为80;及我们可以通过修改和Server有关的配置来实现(ServerProperties)
      (2)修改通用的设置server.XXX;
      (3)修改和Tomcat相关的设置:server.tomcat.xxx
      2.我们可以编写一个EmbeddedServletContainerCustomizer(嵌入式Servlet容器的定制器):

     1     @Bean
     2     public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
     3         return new EmbeddedServletContainerCustomizer() {
     4             //定制嵌入式的Servlet容器相关的规则
     5             @Override
     6             public void customize(ConfigurableEmbeddedServletContainer container) {
     7                     container.setPort(8083);        //设置端口为8083
     8             }
     9         };
    10     }

    二.注册Servlet Filter Listener
      我们可以分别使用ServletRegisterationBean FilterRegisterationBean ServletListenerRegisterationBean完成这三大组件的注册

    --Servlet

     1 package com.zhiyun.springboot.web_restfulcrud.servlet;
     2 
     3 import javax.servlet.ServletException;
     4 import javax.servlet.http.HttpServlet;
     5 import javax.servlet.http.HttpServletRequest;
     6 import javax.servlet.http.HttpServletResponse;
     7 import java.io.IOException;
     8 
     9 /**
    10  * @author : S K Y
    11  * @version :0.0.1
    12  */
    13 public class MyServlet extends HttpServlet {
    14     //处理get()请求
    15     @Override
    16     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    17         doPost(req, resp);
    18     }
    19 
    20     @Override
    21     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    22         resp.getWriter().write("Hello MyServlet");
    23     }
    24 }
     1 package com.zhiyun.springboot.web_restfulcrud.config;
     2 
     3 import com.zhiyun.springboot.web_restfulcrud.servlet.MyServlet;
     4 import org.springframework.boot.web.servlet.ServletRegistrationBean;
     5 import org.springframework.context.annotation.Bean;
     6 import org.springframework.context.annotation.Configuration;
     7 
     8 /**
     9  * @author : S K Y
    10  * @version :0.0.1
    11  */
    12 @Configuration
    13 public class MyServerConfig {
    14     //注册三大组件
    15     @Bean
    16     public ServletRegistrationBean servletRegistrationBean() {
    17         return new ServletRegistrationBean(new MyServlet(), "/myServlet");
    18     }
    19 }

    --Filter

     1 package com.zhiyun.springboot.web_restfulcrud.filter;
     2 
     3 import org.slf4j.Logger;
     4 import org.slf4j.LoggerFactory;
     5 
     6 import javax.servlet.*;
     7 import java.io.IOException;
     8 
     9 /**
    10  * @author : S K Y
    11  * @version :0.0.1
    12  */
    13 public class MyFilter implements Filter {
    14     private Logger logger = LoggerFactory.getLogger(this.getClass());
    15 
    16     @Override
    17     public void init(FilterConfig filterConfig) throws ServletException {
    18 
    19     }
    20 
    21     @Override
    22     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    23         logger.debug("自定义的Filter启用了!");
    24         chain.doFilter(request, response);
    25     }
    26 
    27     @Override
    28     public void destroy() {
    29 
    30     }
    31 }

     

     

    1     @Bean
    2     public FilterRegistrationBean filterRegistrationBean() {
    3         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    4         filterRegistrationBean.setFilter(new MyFilter());
    5         filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
    6         return filterRegistrationBean;
    7     }

    --Listener

     1 package com.zhiyun.springboot.web_restfulcrud.listener;
     2 
     3 import org.slf4j.Logger;
     4 import org.slf4j.LoggerFactory;
     5 
     6 import javax.servlet.ServletContextEvent;
     7 import javax.servlet.ServletContextListener;
     8 
     9 /**
    10  * @author : S K Y
    11  * @version :0.0.1
    12  */
    13 public class MyListener implements ServletContextListener {
    14     private Logger logger = LoggerFactory.getLogger(this.getClass());
    15 
    16     @Override
    17     public void contextInitialized(ServletContextEvent sce) {
    18         logger.debug("contextInitialized...当前web应用启动了");
    19     }
    20 
    21     @Override
    22     public void contextDestroyed(ServletContextEvent sce) {
    23         logger.debug("contextDestroyed...当前web项目销毁");
    24     }
    25 }
    1     @Bean
    2     public ServletListenerRegistrationBean servletListenerRegistrationBean() {
    3         return new ServletListenerRegistrationBean<>(new MyListener());
    4     }

    --由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件.注册三大组件可以采取这样的方法.
    --SpringBoot帮我们自动配置SpringMVC的是惠普,自动的注册SpringMVC的前端控制器:DispatcherServlet.默认拦截"/"所有资源包括静态资源,但是不拦截JSP请求,"/*"会拦截JSP.我们可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径.

    三.使用其他的嵌入式容器
      SpringBoot还支持Jetty(适合开发长连接的应用,例如聊天室),Undertow(不支持JSP):

      1.默认使用了TomCat
      2.切换使用其他Servlet容器,首先需要排除其中的spring-boot-starter-web -->spring-boot-starter-tomcat依赖,而后则可以引入其他的Servlet容器

     1         <!--引入Web模块-->
     2         <dependency>
     3             <groupId>org.springframework.boot</groupId>
     4             <artifactId>spring-boot-starter-web</artifactId>
     5             <exclusions>
     6                 <exclusion>
     7                     <artifactId>spring-boot-starter-tomcat</artifactId>
     8                     <groupId>org.springframework.boot</groupId>
     9                 </exclusion>
    10             </exclusions>
    11         </dependency>
    12 
    13         <!--引入其他的Servlet容器-->
    14         <dependency>
    15             <artifactId>spring-boot-starter-jetty</artifactId>
    16             <groupId>org.springframework.boot</groupId>
    17         </dependency>

    四.嵌入式Servlet容器的自动配置原理
      在SpringBoot中拥有如下自动配置类EmbeddedServletContainerAutoConfiguration:

    1 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    2 @Configuration
    3 @ConditionalOnWebApplication
    4 @Import(BeanPostProcessorsRegistrar.class)
    5 public class EmbeddedServletContainerAutoConfiguration {

       --该类就是嵌入式的Servlet容器自动配置类

     1 /**
     2      * Nested configuration if Tomcat is being used.
     3      */
     4     @Configuration
     5     @ConditionalOnClass({ Servlet.class, Tomcat.class })
     6     @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
     7     public static class EmbeddedTomcat {
     8 
     9         @Bean
    10         public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
    11             return new TomcatEmbeddedServletContainerFactory();
    12         }
    13 
    14     }

      --如果我们导入了Servlet的相关袭来,那么我们就会存在Servlet.class类以及Tomcat.class类,并且容器中不存在EmbeddedServletContainerFactory嵌入式容器工厂(用户自定义的Servlet容器工厂,那么该配置就会生效),在嵌入式容器工厂中定义了如下类:

     1 public interface EmbeddedServletContainerFactory {
     2 
     3     /**
     4      * Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
     5      * Clients should not be able to connect to the returned server until
     6      * {@link EmbeddedServletContainer#start()} is called (which happens when the
     7      * {@link ApplicationContext} has been fully refreshed).
     8      * @param initializers {@link ServletContextInitializer}s that should be applied as
     9      * the container starts
    10      * @return a fully configured and started {@link EmbeddedServletContainer}
    11      * @see EmbeddedServletContainer#stop()
    12      */
    13     EmbeddedServletContainer getEmbeddedServletContainer(
    14             ServletContextInitializer... initializers);
    15 
    16 }

       --获取嵌入式的Servlet容器,在SpringBoot的默认实现中存在如下的实现:

       --以嵌入式Tomcat容器工程为例:

     1     @Override
     2     public EmbeddedServletContainer getEmbeddedServletContainer(
     3             ServletContextInitializer... initializers) {
     4         Tomcat tomcat = new Tomcat();
     5         File baseDir = (this.baseDirectory != null ? this.baseDirectory
     6                 : createTempDir("tomcat"));
     7         tomcat.setBaseDir(baseDir.getAbsolutePath());
     8         Connector connector = new Connector(this.protocol);
     9         tomcat.getService().addConnector(connector);
    10         customizeConnector(connector);
    11         tomcat.setConnector(connector);
    12         tomcat.getHost().setAutoDeploy(false);
    13         configureEngine(tomcat.getEngine());
    14         for (Connector additionalConnector : this.additionalTomcatConnectors) {
    15             tomcat.getService().addConnector(additionalConnector);
    16         }
    17         prepareContext(tomcat.getHost(), initializers);
    18         return getTomcatEmbeddedServletContainer(tomcat);
    19     }

      --可以发现其内部使用Java代码的方式创建了一个Tomcat,并配置了Tomcat工作的基本环境,最终返回一个嵌入式的Tomcat容器

     1 /**
     2      * Create a new {@link TomcatEmbeddedServletContainer} instance.
     3      * @param tomcat the underlying Tomcat server
     4      * @param autoStart if the server should be started
     5      */
     6     public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
     7         Assert.notNull(tomcat, "Tomcat Server must not be null");
     8         this.tomcat = tomcat;
     9         this.autoStart = autoStart;
    10         initialize();
    11     }
    12 
    13     private void initialize() throws EmbeddedServletContainerException {
    14         TomcatEmbeddedServletContainer.logger
    15                 .info("Tomcat initialized with port(s): " + getPortsDescription(false));
    16         synchronized (this.monitor) {
    17             try {
    18                 addInstanceIdToEngineName();
    19                 try {
    20                     // Remove service connectors to that protocol binding doesn't happen
    21                     // yet
    22                     removeServiceConnectors();
    23 
    24                     // Start the server to trigger initialization listeners
    25                     this.tomcat.start();
    26 
    27                     // We can re-throw failure exception directly in the main thread
    28                     rethrowDeferredStartupExceptions();
    29 
    30                     Context context = findContext();
    31                     try {
    32                         ContextBindings.bindClassLoader(context, getNamingToken(context),
    33                                 getClass().getClassLoader());
    34                     }
    35                     catch (NamingException ex) {
    36                         // Naming is not enabled. Continue
    37                     }
    38 
    39                     // Unlike Jetty, all Tomcat threads are daemon threads. We create a
    40                     // blocking non-daemon to stop immediate shutdown
    41                     startDaemonAwaitThread();
    42                 }
    43                 catch (Exception ex) {
    44                     containerCounter.decrementAndGet();
    45                     throw ex;
    46                 }
    47             }
    48             catch (Exception ex) {
    49                 throw new EmbeddedServletContainerException(
    50                         "Unable to start embedded Tomcat", ex);
    51             }
    52         }
    53     }

      --我们对嵌入式容器的配置修改是如何生效的:
      1.修改ServerProperties中的属性
      2.嵌入式Servlet容器定制器:EmbeddedServletContainerCustomizer,帮助我们修改了Sevlet容器的一些默认配置,例如端口号;在EmbeddedServletContainerAutoConfiguration中导入了一个名为BeanPostProcessorsRegistrar,给容器中导入一些组件即嵌入式Servlet容器的后置处理器.后置处理器表示的是在bean初始化前后(创建完对象,还没有赋予初值)执行初始化工作.

      3.EmbeddedServletContainerAutoConfiguration为嵌入式Servlet容器的后置处理器的自动配置类,其存在如下类:

    1     @Override
    2     public Object postProcessBeforeInitialization(Object bean, String beanName)
    3             throws BeansException {
    4         if (bean instanceof ConfigurableEmbeddedServletContainer) {
    5             postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
    6         }
    7         return bean;
    8     }
    1     private void postProcessBeforeInitialization(
    2             ConfigurableEmbeddedServletContainer bean) {
    3         for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
    4             customizer.customize(bean);
    5         }
    6     }
     1     private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
     2         if (this.customizers == null) {
     3             // Look up does not include the parent context
     4             this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
     5                     this.beanFactory
     6                             .getBeansOfType(EmbeddedServletContainerCustomizer.class,
     7                                     false, false)
     8                             .values());
     9             Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
    10             this.customizers = Collections.unmodifiableList(this.customizers);
    11         }
    12         return this.customizers;
    13     }  

      4.获取到了所有的定制器,调用了每一个定制器的customer方法来给Servlet容器进行属性赋值.
      5.ServerProperties也是配置器,因此其配置流程如下:
      (1)SpringBoot根据导入的依赖情况,添加响应的配置容器工厂EmbeddedServletCustomerFactory
      (2)容器中某个组件要创建对象就会惊动后置处理器;
      (3)只要是嵌入式的Servlet容器工厂 后置处理器就工作,从容器中获取所有的EmbeddedServletContainerCustomizer调用定制器的定制方法

    五.嵌入式Servlet容器启动原理
      获取嵌入式的Servlet容器工厂:
      1.SpringBoot引用启动运行run方法;
      2.refreshContext(context);SpringBoot刷新容器并初始化容器,创建容器中的每一个组件:如果是Web应用,创建web的IOC容器AnnotationConfigEmbeddedWebApplicationContext,如果不是则创建AnnotationConfigApplicationContext;
      3.refreshContext(context)刷新刚才创建好的容器
      4.onRefresh():web的IOC容器重写了onRefresh方法;
      5.webIOC容器会创建嵌入式的servlet容器:createEmbeddedServletContainer();
      6.获取嵌入式的servlet容器工厂:EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
      7.使用容器工厂获取嵌入式的Servlet容器;
      8.嵌入式的Servlet容器创建对象,并启动servlet容器.

    六.使用外置的Servlet容器
      嵌入式Servlet容器:
        优点: 简单,快捷
        缺点:默认不支持JSP,优化定制复杂(使用定制器,自定义配置servlet容器的创建工厂);
      --外部的Servlet容器,:外面安装Tomcat-应用war包的方式打包.
      --我们使用war包的形式创建SpringBoot工程可以发现其目录结构如下:

       --创建项目webapp路径及web-XML文件:

       --部署Tomcat服务器:

     

      --创建步骤:
      1.必须创建一个war项目;
      2.将嵌入式的Tomcat指定为provided

    1         <dependency>
    2             <groupId>org.springframework.boot</groupId>
    3             <artifactId>spring-boot-starter-web</artifactId>
    4         </dependency>
    5         <dependency>
    6             <groupId>org.springframework.boot</groupId>
    7             <artifactId>spring-boot-starter-tomcat</artifactId>
    8             <scope>provided</scope>
    9         </dependency>

       3.必须编写一个SpringBootServletInitializer的子类,目的就是调用config方法

     1 package com.skykuqi.springboot.exteralservlet;
     2 
     3 import org.springframework.boot.builder.SpringApplicationBuilder;
     4 import org.springframework.boot.web.support.SpringBootServletInitializer;
     5 
     6 public class ServletInitializer extends SpringBootServletInitializer {
     7 
     8     @Override
     9     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    10         //传入SpringBoot应用的主程序
    11         return application.sources(ExteralservletApplication.class);
    12     }
    13 
    14 }

     七.外置Servlet容器的启动原理
      1.jar包:当我们的应用是使用SpringBoot的jar包形式的话,我们可以直接通过执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器;
      2.war包:启动服务器,服务器启动SpringBoot应用,启用IOC容器:
      3.在Servlet3.0中有一项规范:

      (1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer的实例;
      (2)ServletContainerInitializer的实现必须放在META-INF/services文件夹下,该文件夹下还必须有一个文件名为javax.servlet.ServletContainerInitializer的文件,文件的内容就是ServletContainerInitializer实现的全类名.
      (3)可以使用@HandlesTypes注解来实现,容器在应用启动的时候,加载我们所感兴趣的类.
      4.启动流程:
      (1)启动Tomcat服务器,Spring的Web模块中存在该文件:

       

      (2)SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)所标注的所有这个类型的类都传入到onStartup方法的集合中为这些不是接口不是抽象类类型的类创建实例;
      (3)每一个WebApplicationInitializer的实现类都调用自己的onStartup方法.

       (4)相当于我们的SpringServletContainerInitializer的类会被创建对象,并执行onStartup方法;
      (6)SpringServletContainerInitializer执行onStartup的时候会创建容器

     1     protected WebApplicationContext createRootApplicationContext(
     2             ServletContext servletContext) {
     3         SpringApplicationBuilder builder = createSpringApplicationBuilder();
     4         StandardServletEnvironment environment = new StandardServletEnvironment();
     5         environment.initPropertySources(servletContext, null);
     6         builder.environment(environment);
     7         builder.main(getClass());
     8         ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
     9         if (parent != null) {
    10             this.logger.info("Root context already created (using as parent).");
    11             servletContext.setAttribute(
    12                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
    13             builder.initializers(new ParentContextApplicationContextInitializer(parent));
    14         }
    15         builder.initializers(
    16                 new ServletContextApplicationContextInitializer(servletContext));
    17         builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
    18         builder = configure(builder);
    19         SpringApplication application = builder.build();
    20         if (application.getSources().isEmpty() && AnnotationUtils
    21                 .findAnnotation(getClass(), Configuration.class) != null) {
    22             application.getSources().add(getClass());
    23         }
    24         Assert.state(!application.getSources().isEmpty(),
    25                 "No SpringApplication sources have been defined. Either override the "
    26                         + "configure method or add an @Configuration annotation");
    27         // Ensure error pages are registered
    28         if (this.registerErrorPageFilter) {
    29             application.getSources().add(ErrorPageFilterConfiguration.class);
    30         }
    31         return run(application);
    32     }

       --将创建RootApplicationContext容器,在创建容器时会进行如下操作:
      a.创建SpringApplicationBuilder
      b.在18行调用了configer(),将SpringBoot的主程序类传入了进来
      c.使用builder创建一个Spring应用

  • 相关阅读:
    Java ——if条件语句 switch语句
    Java ——Scanner
    Java ——运算符
    机器学习 涉及内容、模型适用范围 、优缺点总结
    数据的爬取和分析
    文本数据处理
    机器学习【十二】使用管道模型对股票涨幅进行回归分析
    Java ——注释 命名
    Java ——类型转换 向args传递参数
    win10操作系统的安装
  • 原文地址:https://www.cnblogs.com/skykuqi/p/12000060.html
Copyright © 2020-2023  润新知