SpringBoot 第四章WEB(2)

资料

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应用

全部评论

相关推荐

11-04 14:10
东南大学 Java
_可乐多加冰_:去市公司包卖卡的
点赞 评论 收藏
分享
Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务