资料

server.error.include-exception=true

thymeleaf公共页面元素抽取

  • 代码

    1、抽取公共片段
    《div th:fragment="copy">
          © 2011 The Good Thymes Virtual Grocery
    《/div>
    
    2、引入公共片段
    《div th:insert="~{footer :: copy}">《/div>
          ~{templatename::selector}:模板名::选择器
          ~{templatename::fragmentname}:模板名::片段名
    
    3、默认效果:
          insert的公共片段在div标签中
          如果使用th:insert等属性进行引入,可以不用写~{}:
          行内写法可以加上:[[~{}]];[(~{})];
  • 三种引入公共片段的th属性:
    th:insert:将公共片段整个插入到声明引入的元素中
    th:replace:将声明引入的元素替换为公共片段
    th:include:将被引入的片段的内容包含进这个标签中

    《footer th:fragment="copy">
          © 2011 The Good Thymes Virtual Grocery
    《/footer>
    
    引入方式
    《div th:insert="footer :: copy">《/div>
    《div th:replace="footer :: copy">《/div>  #copy id选择器
    《div th:include="footer :: copy">《/div>
    
    效果
    《div>
    《footer>
      © 2011 The Good Thymes Virtual Grocery
    《/footer>
    《/div>
    
    《footer>
    © 2011 The Good Thymes Virtual Grocery
    《/footer>
    
    《div>
    © 2011 The Good Thymes Virtual Grocery
    《/div>
  • 引入片段的时候传入参数: 将同个变量的html片段放在同一个html

    《nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
    《div class="sidebar-sticky">
    《ul class="nav flex-column">
    《li class="nav-item">
    《a class="nav-link active"
          th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"
          href="#" th:href="@{/main.html}">
    《svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
    《path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">《/path>
    《polyline points="9 22 9 12 15 12 15 22">《/polyline>
    《/svg>
          Dashboard 《span class="sr-only">(current)《/span>
    《/a>
    《/li>
    《!--引入侧边栏;传入参数-->
    《div th:replace="commons/bar::#sidebar(activeUri='emps')">《/div>

    高亮效果 可以使用th:class 来进行判断

错误处理机制

springboot默认处理机制

  • 返回一个默认的错误页面 浏览器访问 在ErrorMvcAutoConfiguration中的render方法进行编写
    图片说明

  • 客户端的数据格式是JSON 也就是响应json数据
    图片说明

  • 原理代码

    public class ErrorMvcAutoConfiguration {
    }
    ||
    \/
    public class ErrorMvcAutoConfiguration {}

    DefaultErrorAttributes 帮我们共享页面数据

    ​timestamp:时间戳
    status:状态码 [[${...}]]
    error:错误提示
    exception:异常对象
    message:异常消息
    ​errors:JSR303数据校验的错误都在这里
      @Bean
      @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
      public DefaultErrorAttributes errorAttributes() {
          return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
      }

    errorPageCustomizer:底层发起/error 请求 ,到BasicErrorController

      @Bean
      public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
          return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
      }
    ||
    \/
          @Override
          public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
              ErrorPage errorPage = new ErrorPage(
                      this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
              errorPageRegistry.addErrorPages(errorPage);
          }
    ||
    \/ 响应路径 通过上面的path调用,系统出现错误以后来到error
    请求进行处理;(类似于之前在web.xml注册的错误页面规则) 也就是发起/error请求
    public class ErrorProperties {
      /**
       * Path of the error controller.
       */
      @Value("${error.path:/error}")
      private String path = "/error";
    }

    BasicErrorController :处理默认的/error请求 --->去哪个页面由DefaultErrorViewResolver 决定

      @Bean
      @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
      public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
              ObjectProvider<ErrorViewResolver> errorViewResolvers) {
          return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                  errorViewResolvers.orderedStream().collect(Collectors.toList()));
      }
    ||
    \/
    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
    //产生html
         @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
      public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    //获得状态码
          HttpStatus status = getStatus(request);
          Map<String, Object> model = Collections
                  .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
          response.setStatus(status.value());
    //ModelAndView 要去的页面地址
          ModelAndView modelAndView = resolveErrorView(request, response, status, model);
          return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
      }
    //产生json数据效果,其他请求
      @RequestMapping
      public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
          HttpStatus status = getStatus(request);
          if (status == HttpStatus.NO_CONTENT) {
              return new ResponseEntity<>(status);
          }
          Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
          return new ResponseEntity<>(body, status);
      }
    }
    ||
    \/   modelAndView响应页面
      protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
              Map<String, Object> model) {
    //拿到所有的异常视图得到modelAndView--->errorViewResolvers 
    //刚好我们有一个默认的errorViewResolver(DefaultErrorViewResolver )
          for (ErrorViewResolver resolver : this.errorViewResolvers) {
              ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
              if (modelAndView != null) {
                  return modelAndView;
              }
          }
          return null;
      }

    DefaultErrorViewResolver

      @Bean
      @ConditionalOnBean(DispatcherServlet.class)
      @ConditionalOnMissingBean(ErrorViewResolver.class)
      DefaultErrorViewResolver conventionErrorViewResolver() {
          return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
      }
    ||
    \/
    public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
      private static final Map<Series, String> SERIES_VIEWS;
    //static 先放入4xx 5xx
      static {
          Map<Series, String> views = new EnumMap<>(Series.class);
          views.put(Series.CLIENT_ERROR, "4xx");
          views.put(Series.SERVER_ERROR, "5xx");
          SERIES_VIEWS = Collections.unmodifiableMap(views);
      }

    浏览器在请求的请求头中优先接收text/html
    图片说明

    客户端在请求的请求头中是/*
    图片说明

  • 步骤:
    一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
    1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
    2)有模板引擎的情况下;error/状态码;** 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

    我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);页面能获取的信息;
    我们可以自己配置一个404.html 手动不用配置到springboot中 springboot已经帮我们配置好了 直接在templates/error/404.html 或者4xx可以使用所有的页面
    图片说明
    3)没有模板引擎(模板引擎找不到这个错误页面),静态资源static文件夹下找;
    4)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;相当于我们没有配置

响应一串自己定制错误的json数据

  • 自定义一个Exception类

    public class UserException extends RuntimeException{
      public UserException() {
          super("not uesr----->my Exception");
      }
    }

    图片说明
    该json数据都放在了request中 我们可以通过页面来获取

    status:[[${status}]]   br>
    exception:[[${exception}]]    br>
    message:[[${message}]]    br>

    图片说明

  • 自定义json数据

    @ControllerAdvice
    public class MyErrorHandler{
      @ResponseBody
      @ExceptionHandler(UserException.class)
      public Map<String ,Object> handlerException(Exception e){
          Map<String ,Object> map = new HashMap<>();
          map.put("myCode","user.NotExist");
          map.put("myMessage",e.getMessage());
          return map;
      }
    }

    图片说明
    没经过任何处理 连页面都是json数据串
    图片说明

  • 自适应 效果 需要联想到 springboot 内部已配置的自适应效果
    通过转发到/error
    图片说明
    但响应状态码的情况是:status_code没改

      protected HttpStatus getStatus(HttpServletRequest request) {
          Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
          if (statusCode == null) {
              return HttpStatus.INTERNAL_SERVER_ERROR;
          }
          try {
              return HttpStatus.valueOf(statusCode);
          }
          catch (Exception ex) {
              return HttpStatus.INTERNAL_SERVER_ERROR;
          }
      }
  • 将我们的定制数据携带出去 map
    出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

  1. 完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;【麻烦】
  2. 页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
    容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
    图片说明
    //给容器中加入我们自定义的ErrorAttributes
    @Component
    public class MyErrorAttributes extends DefaultErrorAttributes {
     // 自定义ErrorAttributes 改变默认行为
     @Override
     public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
         Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
         //异常处理器
         Map<String, Object> exc = (Map<String, Object>) webRequest.getAttribute("exc", 0);//参数0 从请求域中获取
         map.put("exc",exc);
         return map;
     }
    }
    @ControllerAdvice
    class MyErrorHandler{
     @ExceptionHandler(UserException.class)
     public String handlerException(Exception e, HttpServletRequest request){
         //通过分析
         System.out.println("MyHandlerException........");
         Map<String ,Object> map = new HashMap<>();
         request.setAttribute("javax.servlet.error.status_code","500");
         map.put("myCode","user.NotExist");
         map.put("myMessage",e.getMessage());
         request.setAttribute("exc",map);
         //---> request ---> DefaultErrorAttributes
         return "forward:/error";
     }
    }
    浏览器直接从exc中去即可

配置嵌入式Servlet容器

  • 修改和server有关的配置(ServerProperties【底层也是EmbeddedServletContainerCustomizer】);

    server.port=8081
    server.context-path=/crud
    
    server.tomcat.uri-encoding=UTF-8
    
    //通用的Servlet容器设置
    server.xxx
    //Tomcat的设置
    server.tomcat.xxx
  • 编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
    在配置类中编写

    @Bean  //一定要将这个定制器加入到容器中
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
      return new EmbeddedServletContainerCustomizer() {
    
          //定制嵌入式的Servlet容器相关的规则
          @Override
          public void customize(ConfigurableEmbeddedServletContainer container) {
              container.setPort(8083);
          }
      };
    }
  • xxxxCustomizer 可以帮助我们进行定制化配置

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

原先注册这些组件是web.xml 来注册

  • Servlet

      @Bean
      public ServletRegistrationBean servletRegistrationBean(){
          MyServlet servlet = new MyServlet();
          ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet,"/myServlet");
          return registrationBean;
      }
    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,httpServlet");
      }
    }

    图片说明

  • Filter

      @Bean
      public FilterRegistrationBean filterRegistrationBean(){
          FilterRegistrationBean registrationBean = new FilterRegistrationBean(new MyFilter());
          registrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
          return registrationBean;
      }
    public class MyFilter implements Filter {
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
          System.out.println("初始化");
      }
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
          System.out.println("doFilter....");
          filterChain.doFilter(servletRequest,servletResponse);
      }
      @Override
      public void destroy() {
          System.out.println("销毁");
      }
    }

    图片说明

  • Listener

      @Bean
      public ServletListenerRegistrationBean servletListenerRegistrationBean(){
          ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean();
          registrationBean.setListener(new MyListener());
          return registrationBean;
      }
    public class MyListener implements ServletContextListener {
      @Override
      public void contextInitialized(ServletContextEvent sce) {
          System.out.println("init");
      }
      @Override
      public void contextDestroyed(ServletContextEvent sce) {
          System.out.println("destroy");
      }
    }

    图片说明

  • SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
    DispatcherServletAutoConfiguration中:

    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
    @Configuration(proxyBeanMethods = false)
      @Conditional(DispatcherServletRegistrationCondition.class)
      @ConditionalOnClass(ServletRegistration.class)
      @EnableConfigurationProperties(WebMvcProperties.class)
      @Import(DispatcherServletConfiguration.class)
      protected static class DispatcherServletRegistrationConfiguration {
    
          @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
          @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
          public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                  WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
              DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                      webMvcProperties.getServlet().getPath());
              registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    //    在配置路径找到对应的配置文件 默认拦截    private String path = "/";
      //默认拦截: /  所有请求;包静态资源,但是不拦截jsp请求;   /*会拦截jsp
      //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径    registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
              multipartConfig.ifAvailable(registration::setMultipartConfig);
              return registration;
          }
      }

切换其他的servlet容器 我们默认是tomcat

  • Tomcat(默认使用)

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
     引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
    </dependency>
  • 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>
    <!--引入其他的Servlet容器-->
    <dependency>
     <artifactId>spring-boot-starter-jetty</artifactId>
     <groupId>org.springframework.boot</groupId>
    </dependency>
  • Undertow

    <!-- 引入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>
    <!--引入其他的Servlet容器-->
    <dependency>
     <artifactId>spring-boot-starter-undertow</artifactId>
     <groupId>org.springframework.boot</groupId>
    </dependency>
  • 三个容器在EmbeddedWebServerFactoryCustomizerAutoConfiguration中进行注册
    以tomcat为例子

    public WebServer getWebServer(ServletContextInitializer... initializers) {
          if (this.disableMBeanRegistry) {
              Registry.disableRegistry();
          }
    //在这里new一个tomcat
          Tomcat tomcat = new Tomcat();
          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);
          return this.getTomcatWebServer(tomcat);
      }
    ||
    \/
    return new TomcatWebServer(tomcat, this.getPort() >= 0);大于等于0调用方法
    ||
    \/
      public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
          this.monitor = new Object();
          this.serviceConnectors = new HashMap();
          Assert.notNull(tomcat, "Tomcat Server must not be null");
          this.tomcat = tomcat;
          this.autoStart = autoStart;
          this.initialize();
      }
    ||
    \/
    //初始化 条件大于等于0
     private void initialize() throws WebServerException {
          logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
          synchronized(this.monitor) {
              try {
                  this.addInstanceIdToEngineName();
                  Context context = this.findContext();
                  context.addLifecycleListener((event) -> {
                      if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                          this.removeServiceConnectors();
                      }
                  });
                  this.tomcat.start();
                  this.rethrowDeferredStartupExceptions();
                  try {
                      ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                  } catch (NamingException var5) {
                  }
                  this.startDaemonAwaitThread();
              } catch (Exception var6) {
                  this.stopSilently();
                  this.destroySilently();
                  throw new WebServerException("Unable to start embedded Tomcat", var6);
              }
          }
      }

EmbeddedWebServerFactoryCustomizerAutoConfiguration:定制器帮我们修改了Servlet容器的配置?
怎么修改的原理?

  • 容器中导入了WebServerFactoryCustomizerBeanPostProcessor 控制处理器
    //初始化之前
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    ////如果当前初始化的是一个WebServerFactory类型的组件
          if (bean instanceof WebServerFactory) {
              this.postProcessBeforeInitialization((WebServerFactory)bean);
          }
          return bean;
      }
    ||
    \/
     private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
          ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> 
    //定制器
              customizer.customize(webServerFactory);
          });
      }
    ||
    \/
      private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
          if (this.customizers == null) {
              this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
              this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
              this.customizers = Collections.unmodifiableList(this.customizers);
          }
          return this.customizers;
      }
  • 步骤:[推荐用配置文件]
    1、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedWebServerFactoryCustomizerAutoConfiguration【TomcatWebServerFactoryCustomizer】
    2、容器中某个组件要创建对象就会惊动后置处理器;WebServerFactoryCustomizerBeanPostProcessor;
    只要是嵌入式的Servlet容器工厂,后置处理器就工作;
    3、后置处理器,从容器中获取所有的TomcatWebServerFactoryCustomizer,调用定制器的定制方法

使用外置的Servlet容器

嵌入式Servlet容器:应用打成可执行的jar
​1. 优点:简单、便携;
2.​ 缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;

步骤

  • 必须创建一个war项目;(利用idea创建好目录结构)
    图片说明

  • 将嵌入式的Tomcat指定为provided;

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
     <scope>provided</scope>
    </dependency>
  • 必须编写一个SpringBootServletInitializer的子类,并调用configure方法【固定写法】

    public class ServletInitializer extends SpringBootServletInitializer {
    
     @Override
     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
         //传入SpringBoot应用的主程序SpringBootWebJspApplicationxxx    
        return application.sources(SpringBootWebJspApplicationxxx.class);
     }
    }
  • 添加一个tomcat服务器

  • 启动服务器就可以使用;

原理

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;

war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

servlet3.0(Spring注解版):
规则:

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

流程:

  • 启动Tomcat

  • \META-INF\services\javax.servlet.ServletContainerInitializer,Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
    图片说明

    @HandlesTypes({WebApplicationInitializer.class})
    public class SpringServletContainerInitializer implements ServletContainerInitializer { 
     public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {}
    }
  • SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
    图片说明

  • 每一个WebApplicationInitializer都调用自己的onStartup

    public interface WebApplicationInitializer {
      void onStartup(ServletContext var1) throws ServletException;
    }
  • 相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

  • SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

      public void onStartup(ServletContext servletContext) throws ServletException {
          this.logger = LogFactory.getLog(this.getClass());
          WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
          if (rootAppContext != null) {
              servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                  public void contextInitialized(ServletContextEvent event) {
                  }
              });
          } else {
              this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
          }
      }
  • Spring的应用就启动并且创建IOC容器

      protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
          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() && 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));
          }
    
          return this.run(application);  <-=run   自定义的容器过程
      }
  • 启动Servlet容器,再启动SpringBoot应用