首页 > 其他分享 >SpringBoot--嵌入式Servlet容器

SpringBoot--嵌入式Servlet容器

时间:2022-08-21 02:57:18浏览次数:107  
标签:容器 SpringBoot Tomcat -- 嵌入式 import Servlet public

一、嵌入式Servlet容器

在传统的Web开发中,需要将项目打成 war 包,在外部配置部署好 Tomcat 服务器,而这个 Tomcat 就是 Servlet 容器,在使用 SpringBoot 开发时,我们无需再外部部署 Servlet 容器,使用的是嵌入式(内置) Servlet 容器( Tomcat ),如果我们使用嵌入式 Servlet 容器,存在以下问题:

  • 1、如果我们是在外部安装了 Tomcat ,如果我们想要进行自定义的配置优化,可以在其 conf 文件夹下修改配置文件来实现,或者通过在 pom.xml 中导入 Tomcat 插件实现。

  • 2、而在使用内置 Servlet 容器时,我们可以使用如下方法来修改 Servlet 容器的相关配置:

    • (1)例如我们可以在 SpringBoot 项目中的 application.yml 文件中编写 server.port=80 来修改我们的启用端口为80 ;
    • (2)可以通过修改和 Servlet 有关的配置来实现( ServetProperties );
    • (3)可以通过修改通用的设置: server.xxx ;
    • (4)可以通过修改和Tomcat相关的设置: server.tomcat.xxx ;
    • (5)可以编写一个 EmbeddedServletContainerCustomizer (嵌入式 Servlet 容器的定制器)
    @Bean
       public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
           return new EmbeddedServletContainerCustomizer() {
               //定制嵌入式的Servlet容器相关的规则
               @Override
               public void customize(ConfigurableEmbeddedServletContainer container) {
                       container.setPort(8083);        //设置端口为8083
               }
           };
       }
    

二、注册 Servlet 、 Filter 、 Listener

  • 由于 SpringBoot 默认是以 jar 包的方式通过嵌入式 Servlet 容器来启动 SpringBoot 的 Web 应用,没有 web.xml 文件,我们可以分别使用 ServletRegisterationBean 、 FilterRegisterationBean 、 ServletListenerRegisterationBean 完成 Web 三大组件的注册

    • —— Servlet

      package com.itheima.Servlet;
      
      import ```javax.servlet.ServletException;
      import ```javax.servlet.http.HttpServlet;
      import ```javax.servlet.http.HttpServletRequest;
      import ```javax.servlet.http.HttpServletResponse;
      import ```java.io.IOException;
      
      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");
         }
      }
      
      package com.itheima.config;
      
      import com.itheima.Servlet.MyServlet;
      import org.springframework.boot.web.servlet.ServletRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class MyServerConfig {
         //注册三大组件之一:Servlet
         @Bean
         public ServletRegistrationBean servletRegistrationBean() {
             return new ServletRegistrationBean(new MyServlet(), "/myServlet");
         }
      }
      
    • —— Filter

      package com.itheima.Filter;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import ```javax.servlet.*;
      import ```java.io.IOException;
      
      public class MyFilter implements Filter {
         private Logger logger = LoggerFactory.getLogger(this.getClass());
      
         @Override
         public void init(FilterConfig filterConfig) throws ServletException {
         }
         @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
             logger.debug("自定义的Filter启用了!");
             chain.doFilter(request, response);
         }
         @Override
         public void destroy() {
         }
      }
      
      package com.itheima.config;
      
      import com.itheima.Filter.MyFilter;
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import ```java.util.Arrays;
      
      @Configuration
      public class MyFilterConfig {
         //注册三大组件之一:Filter
         @Bean
         public FilterRegistrationBean filterRegistrationBean() {
             FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
             filterRegistrationBean.setFilter(new MyFilter());
             filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
             return filterRegistrationBean;
         }
      }
      
    • —— Listener

      package com.itheima.Listener;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import ```javax.servlet.ServletContextEvent;
      import ```javax.servlet.ServletContextListener;
      
      public class MyListener implements ServletContextListener {
         private Logger logger = LoggerFactory.getLogger(this.getClass());
      
         @Override
         public void contextInitialized(ServletContextEvent sce) {
             logger.debug("contextInitialized...当前web应用启动了");
         }
         @Override
         public void contextDestroyed(ServletContextEvent sce) {
             logger.debug("contextDestroyed...当前web项目销毁");
         }
      }
      
      package com.itheima.config;
      
      import com.itheima.Listener.MyListener;
      import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class MyListenerConfig {
         //注册三大组件之一:Listener
         @Bean
         public ServletListenerRegistrationBean servletListenerRegistrationBean() {
             return new ServletListenerRegistrationBean<>(new MyListener());
         }
      }
      
  • SpringBoot 帮我们自动配置 SpringMVC ,自动的注册 SpringMVC 的前端控制器: DispatcherServlet ,默认拦截 "/" 所有资源,包括静态资源,但是不拦截 JSP 请求, "/*" 会拦截 JSP ,我们可以通过 server.servletPath 来修改 SpringMVC 前端控制器默认拦截的请求路径

三、嵌入式 Servlet 容器的自动配置原理

在 SpringBoot 中拥有如下自动配置类 EmbeddedServletContainerAutoConfiguration :

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {}
  • 该类就是嵌入式的 Servlet 容器自动配置类
/**
    * Nested configuration if Tomcat is being used.
    */
   @Configuration
   @ConditionalOnClass({ Servlet.class, Tomcat.class })
   @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
   public static class EmbeddedTomcat {

       @Bean
       public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
           return new TomcatEmbeddedServletContainerFactory();
       }

   }
  • 如果我们导入了 Servlet 的相关依赖,那么我们就会存在 Servlet.class 类以及 Tomcat.class 类,并且容器中不存在 EmbeddedServletContainerFactory 嵌入式容器工厂(用户自定义的 Servlet 容器工厂,那么该配置就会生效),在嵌入式容器工厂中定义了如下类:
public interface EmbeddedServletContainerFactory {
   /**
   Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
   Clients should not be able to connect to the returned server until
   {@link EmbeddedServletContainer#start()} is called (which happens when the
   {@link ApplicationContext} has been fully refreshed).
   @param initializers {@link ServletContextInitializer}s that should be applied as
   the container starts
   @return a fully configured and started {@link EmbeddedServletContainer}
   @see EmbeddedServletContainer#stop()
    */
   /**
   获取一个新的完全配置但暂停的 {@link EmbeddedServletContainer} 实例。
   在调用 {@link EmbeddedServletContainer#start()} 之前,客户端应该无法连接到返回的服务器(这在 	{@link ApplicationContext} 已完全刷新时发生)。
   @param 初始化器 {@link ServletContextInitializer} 应在容器启动时应用 
   @return 完全配置并启动 {@link EmbeddedServletContainer} 
   @see EmbeddedServletContainer#stop()
    */
   EmbeddedServletContainer getEmbeddedServletContainer(
           ServletContextInitializer... initializers);

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

image

  • 以嵌入式 Tomcat 容器工厂为例:
@Override
   public EmbeddedServletContainer getEmbeddedServletContainer(
           ServletContextInitializer... initializers) {
       Tomcat tomcat = new Tomcat();
       File baseDir = (this.baseDirectory != null ? this.baseDirectory
               : createTempDir("tomcat"));
       tomcat.setBaseDir(baseDir.getAbsolutePath());
       Connector connector = new Connector(this.protocol);
       tomcat.getService().addConnector(connector);
       customizeConnector(connector);
       tomcat.setConnector(connector);
       tomcat.getHost().setAutoDeploy(false);
       configureEngine(tomcat.getEngine());
       for (Connector additionalConnector : this.additionalTomcatConnectors) {
           tomcat.getService().addConnector(additionalConnector);
       }
       prepareContext(tomcat.getHost(), initializers);
       return getTomcatEmbeddedServletContainer(tomcat);
   }
  • 可以发现其内部使用 Java 代码的方式创建了一个 Tomcat ,并配置了 Tomcat 工作的基本环境,最终返回一个嵌入式的 Tomcat 容器
/**
Create a new {@link TomcatEmbeddedServletContainer} instance.
@param tomcat the underlying Tomcat server
@param autoStart if the server should be started
*/
/**
创建一个新的 {@link TomcatEmbeddedServletContainer} 实例。
@param tomcat 底层是 Tomcat 服务器 
@param autoStart 是否应该启动服务器
*/
   public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
       Assert.notNull(tomcat, "Tomcat Server must not be null");
       this.tomcat = tomcat;
       this.autoStart = autoStart;
       initialize();
   }

   private void initialize() throws EmbeddedServletContainerException {
       TomcatEmbeddedServletContainer.logger
               .info("Tomcat initialized with port(s): " + getPortsDescription(false));
       synchronized (this.monitor) {
           try {
               addInstanceIdToEngineName();
               try {
                   // Remove service connectors to that protocol binding doesn't happen yet
                   // 尚未删除与该协议绑定的服务连接器
                   removeServiceConnectors();

                   // Start the server to trigger initialization listeners
                   // 启动服务器触发初始化监听器
                   this.tomcat.start();

                   // We can re-throw failure exception directly in the main thread
                   // 我们可以直接在主线程中重新抛出失败异常
                   rethrowDeferredStartupExceptions();

                   Context context = findContext();
                   try {
                       ContextBindings.bindClassLoader(context, getNamingToken(context),
                               getClass().getClassLoader());
                   }
                   catch (NamingException ex) {
                       // Naming is not enabled. Continue
                       // 未启用命名。继续
                   }

                   // Unlike Jetty, all Tomcat threads are daemon threads. We create a blocking non-daemon to stop immediate shutdown
                   
                   // 与 Jetty 不同,所有 Tomcat 线程都是守护线程。我们创建一个阻塞的非守护进程来停止立即关闭
                   startDaemonAwaitThread();
               }
               catch (Exception ex) {
                   containerCounter.decrementAndGet();
                   throw ex;
               }
           }
           catch (Exception ex) {
               throw new EmbeddedServletContainerException(
                       "Unable to start embedded Tomcat", ex);
           }
       }
   }
  • 我们对嵌入式容器的配置修改是如何生效的:

    • 1、修改 ServletProperties 中的属性
    • 2、嵌入式 Servlet 容器定制器: EmbeddedServletContainerCustomizer ,帮助我们修改了 Servlet 容器的一些默认配置,例如端口号;在 EmbeddedServletContainerAutoConfiguration 中导入了一个名为 BeanPostProcessorsRegistrar ,给容器中导入一些组件即嵌入式 Servlet 容器的后置处理器。后置处理器表示的是在 bean 初始化前后(创建完对象,还没有赋予初始值)执行初始化工作。
    • 3、 EmbeddedServletContainerAutoConfiguration 为嵌入式 Servlet 容器的后置处理器的自动配置类,其存在如下类:
    @Override
       public Object postProcessBeforeInitialization(Object bean, String beanName)
               throws BeansException {
           if (bean instanceof ConfigurableEmbeddedServletContainer) {
               postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
           }
           return bean;
       }
    
    private void postProcessBeforeInitialization(
               ConfigurableEmbeddedServletContainer bean) {
           for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
               customizer.customize(bean);
           }
       }
    
    private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
           if (this.customizers == null) {
               // Look up does not include the parent context
               this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                       this.beanFactory
                               .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                       false, false)
                               .values());
               Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
               this.customizers = Collections.unmodifiableList(this.customizers);
           }
           return this.customizers;
       }
    
    • 4、获取到了所有的定制器,调用了每一个定制器的 customer 方法来给 Servlet 容器进行属性赋值
    • 5、 ServletProperties 也是配置器,因此其配置流程如下:
      • (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、 web 的 IOC 容器会创建嵌入式的 servlet 容器: createEmbeddedServletContainer()
  • 6、获取嵌入式的 servlet 容器工厂: EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory()
  • 7、使用容器工厂获取嵌入式的 Servlet 容器
  • 8、嵌入式的 Servlet 容器创建对象,并启动 servlet 容器

五、使用外置的 Servlet 容器

嵌入式 Servlet 容器

  • 优点:简单、快捷
  • 缺点:默认不支持JSP,优化定制复杂(使用定制器,自定义配置 Servlet 容器的创建工厂)

外部的 Servlet 容器:外面安装 Tomcat -应用 war 包的方式打包

我们使用 war 包的形式创建 SpringBoot 工程可以发现其目录结构如下:

image

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

image

  • 部署 Tomcat 服务器

image

image

  • 创建步骤:

    • (1)必须创建一个 war 项目
    • (2)将嵌入式的 Tomcat 指定为 provided
     	<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>
    
    • (3)必须编写一个 SpringBootServletInitializer 的子类,目的就是调用 config 方法
    package com.skykuqi.springboot.exteralservlet;
    
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    
    public class ServletInitializer extends SpringBootServletInitializer {
    
       @Override
       protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
           //传入SpringBoot应用的主程序
           return application.sources(ExteralservletApplication.class);
       }
    }
    

六、外置 Servlet 容器的启动原理

  • 1、 jar 包:当我们的应用是使用 SpringBoot 的 jar 包形式的话,我们可以直接通过执行 SpringBoot 主类的 main 方法,启动 IOC 容器,创建嵌入式的 Servlet 容器

  • 2、 war 包:启动服务器,服务器启动 SpringBoot 应用,启用 IOC 容器

  • 3、在 Servlet3.0 中有一项规范:

    image

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

    • (1)启动 Tomcat 服务器, Spring 的 Web 模块中存在该文件:

      image

      image

    • (2) SpringServletContainerInitialize 将 @HandlesTypes(WebApplicationInitializer.class) 所标注的所有这个类型的类都传入到 onStartup 方法的集合中为这些不是接口不是抽象类类型的类创建实例

    • (3)每一个 WebApplicationInitializer 的实现类都调用自己的 onStartup 方法

      image

    • (4)相当于我们的 SpringServletContainerInitializer 的类会被创建对象,并执行 onStartup 方法

    • (5) SpringServletContainerInitializer 执行 onStartup 的时候会创建容器

      protected WebApplicationContext createRootApplicationContext(
                 ServletContext servletContext) {
             SpringApplicationBuilder builder = createSpringApplicationBuilder();
             StandardServletEnvironment environment = new StandardServletEnvironment();
             environment.initPropertySources(servletContext, null);
             builder.environment(environment);
             builder.main(getClass());
             ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
             if (parent != null) {
                 this.logger.info("Root context already created (using as parent).");
                 servletContext.setAttribute(
                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
                 builder.initializers(new ParentContextApplicationContextInitializer(parent));
             }
             builder.initializers(
                     new ServletContextApplicationContextInitializer(servletContext));
             builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
             builder = configure(builder);
             SpringApplication application = builder.build();
             if (application.getSources().isEmpty() && AnnotationUtils
                     .findAnnotation(getClass(), Configuration.class) != null) {
                 application.getSources().add(getClass());
             }
             Assert.state(!application.getSources().isEmpty(),
                     "No SpringApplication sources have been defined. Either override the "
                             + "configure method or add an @Configuration annotation");
             // Ensure error pages are registered
             if (this.registerErrorPageFilter) {
                 application.getSources().add(ErrorPageFilterConfiguration.class);
             }
             return run(application);
         }
      
      • 将创建 RootApplicationContext 容器,在创建容器时会进行如下操作
        • a.创建 SpringApplicationBuilder
        • b.在18行调用了 configer() ,将 SpringBoot 的主程序类传入了进来
        • c.使用 builder 创建一个 Spring 应用

参考博客:https://www.cnblogs.com/skykuqi/p/12000060.html

标签:容器,SpringBoot,Tomcat,--,嵌入式,import,Servlet,public
From: https://www.cnblogs.com/cxy-lxl/p/16609238.html

相关文章

  • 【luogu P2508】圆上的整点(高斯素数模板)
    圆上的整点题目链接:luoguP2508题目大意给你一个圆,问你圆周上有多少个点的坐标是整点。思路考虑一个东西叫做高斯整数。其实它是复数,是\(a+bi\)中\(a,b\)都是整......
  • ERROR--Disconnected from the target VM, address : '127.0.0.1:6847' , transport :
    DisconnectedfromthetargetVM,address:'127.0.0.1:6847',transport:'socket'原因分析1服务器采用的Tomcat,编译打包方式未设置war【默认打包方式是jar】......
  • ERROR--Unable to resolve column XXXX
    IDEA报错UnabletoresolvecolumnXXXX解决办法。原因分析IDEA无法完全识别数据库的信息场景在IDEA中的Mybatis需要书写SQL语句时,编译过程会报警告idea......
  • ERROR--"Unable to process Jar entry [module-info.class] from Jar"
    UnabletoprocessJarentry[module-info.class]fromJar原因分析无法从Jar包中处理jar条目[module-info.class]场景启动Idea项目时会出现严重的红色错误......
  • 一些统计量
    均值(Mean):\[\overline{x}=\frac{1}{n}\sum_{i=1}^{n}x_i\]方差(Variance):衡量单类样本偏离均值的程度\[D(x)=\frac{1}{n}\sum_{i=1}^{n}(x_i-\overline{x})^2\]......
  • Python custom modify the __add__ method All In One
    PythoncustommodifytheaddmethodAllInOnePython改写__add__类方法"""#classJuice:#def__init__(self,name,capacity):#self.na......
  • git--提交日志规范
    对于版本控制工具来说,尤为重要的就是每次提交版本到代码库的日志撰写。清晰、规范、格式化的提交日志有助于追踪版本修改,查看历史记录等。Git不允许提交日志为空,这里推荐......
  • 删除链表结点类问题
    删除链表结点NO1.删除链表倒数第k个结点给定一个链表,删除链表的倒数第n个节点并返回链表的头指针。要求:空间复杂度\(O(1)\),时间复杂度\(O(n)\)如果倒数第k个......
  • Spring 04: IOC控制反转 + DI依赖注入
    Spring中的IOC一种思想,两种实现方式IOC(InversionofControl):控制反转,是一种概念和思想,指由Spring容器完成对象创建和依赖注入核心业务:(a)对象的创建(b)依赖的......
  • [Ynoi2016] 炸脖龙 I
    题目传送门已经能过hack,原因:做快速幂的时候需要微判一下边界。很好奇lxl为什么不卡显然区间加可用线段树做。然后操作二用扩展欧拉定理,每个\(p\)最多递归\(\log\)......