欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot入门学习笔记(二、日志与Web开发)

程序员文章站 2022-06-12 19:16:45
...

三、SpringBoot与日志

SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
开发时,不应该直接调用日志的实现类,而是应该调用日志抽象层里面的方法。
导入slf4j的jar包和logback的实现jar包

//slf4j官方示例
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

SpringBoot入门学习笔记(二、日志与Web开发)
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件
当不同框架使用了不同的日志框架后,统一日志记录,可以使用slf4j提供的jar替换框架里的日志框架,实现所有框架共同使用slf4j。
SpringBoot入门学习笔记(二、日志与Web开发)

将日志框架统一成slf4j的步骤:

  1. 将系统中的其他日志框架先排除出去
  2. 用中间包来替换原有的日志框架
  3. 导入slf4j其他的实现

1. SpringBoot的日志关系

SpringBoot使用它来做日志功能:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
      <version>2.3.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>

SpringBoot入门学习笔记(二、日志与Web开发)
可以看到SpringBoot的日志jar包在底层的依赖关系
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架时,只需要把引入的框架依赖的日志框架去除掉。

2. 日志的关系

  1. 默认配置
    SpringBoot默认配置好了日志。
    测试log,测试类
package com.angenin.springboot;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBoot03LoggingApplicationTests {

    //记录器
    Logger logger = LoggerFactory.getLogger(getClass());

    @Test
    void contextLoads() {
        //日志的级别:
        //由低到高  trace < debug < info < warn < error
        //可以调整输出(打印)的日志级别,低级别的日志就不会输出

        //跟踪
        logger.trace("trace日志。。");
        //debug
        logger.debug("debug日志。。");
        //自定义,SpringBoot默认使用info级别(root级别),即上面两个即使写在方法里也不会输出打印
        //可以在application.properties加入 logging.level.包名=trace 属性指定包下使用的日志输出级别
        logger.info("info日志。。");
        //警告
        logger.warn("warn日志。。");
        //错误
        logger.error("error日志。。");
    }
}

在application.properties中

#指定包下使用的日志输出级别
logging.level.com.angenin=trace
#在指定的路径下生成日志文件(在当前项目的根目录下生成spring/log/spring.log日志文件)
logging.file.path=spring/log
#在控制台输入的日志格式
logging.pattern.console=
#指定生成的日志文件中的输出格式
logging.pattern.file=

日志输出格式:
SpringBoot入门学习笔记(二、日志与Web开发)

  1. 指定配置
    logback指定配置文件:在resources下新建logback.xml或logback-spring.xml文件,SpringBoot就不会用底层的默认配置,而是使用resources下的配置文件。
    SpringBoot入门学习笔记(二、日志与Web开发)
    SpringBoot推荐我们配置文件名写为logback-spring.xml。
    logback.xml:会直接被日志框架识别
    logback-spring.xml:不会被日志框架直接加载,而是由SpringBoot解析日志配置,所以在配置文件中可使用SpringBoot提供的< springProfile >标签(< springProfile >标签可以指定某段配置文件只在某个环境下生效)。
<!--官方的示例-->
<springProfile name="staging">
	<!--在staging环境下生效-->
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
	<!--在dev和staging环境下生效-->
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
	<!--在不是production环境下生效,production环境下不生效-->
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

四、SpringBoot与Web开发

1. SpringBoot对静态资源的映射规则

WebMvcAutoConfiguration.class:

        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }
@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {...}
  1. 所有访问/webjars/**,都去classpath:/META-INF/resources/webjars/下找资源
    webjars:以jar包的方式引入静态资源

       <!--   引入jquery-webjar     -->
       <!--  在访问的时候只需要写webjars下面资源的名称即可   -->
       <dependency>
           <groupId>org.webjars</groupId>
           <artifactId>jquery</artifactId>
           <version>3.4.1</version>
       </dependency>
    

    SpringBoot入门学习笔记(二、日志与Web开发)
    访问JQuery的路径:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
    SpringBoot入门学习笔记(二、日志与Web开发)

  2. /**访问当前项目的任何资源,会到下面5个目录(静态资源目录)找资源:

    classpath:/		为resources目录下
    "classpath:/META-INF/resources/"	优先级最高
    "classpath:/resources/"
    "classpath:/static/"
    "classpath:/public/"
    "/"				当前项目的根目录		优先级最低
    

    如果在resources/static下有a.html页面,访问的路径:http://localhost:8080/a.html
    SpringBoot入门学习笔记(二、日志与Web开发)

  3. 欢迎页,5个静态资源目录下的所有index.html,被/**路径映射。
    下面是欢迎页的SpringBoot源码
    WebMvcAutoConfiguration.class

        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
            return welcomePageHandlerMapping;
        }
    
            private Optional<Resource> getWelcomePage() {
            String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
            return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
        }
    
        private Resource getIndexHtml(String location) {
            return this.resourceLoader.getResource(location + "index.html");
        }
    

    WelcomePageHandlerMapping.class

    	WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
    	        if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
    	            logger.info("Adding welcome page: " + welcomePage.get());
    	            this.setRootViewName("forward:index.html");
    	        } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
    	            logger.info("Adding welcome page template: index");
    	            this.setRootViewName("index");
    	        }
    	
    	    }
    

    如果5个路径静态资源路径都有index.html,会使用resources/META-INF/resources/index.html
    SpringBoot入门学习笔记(二、日志与Web开发)

  4. 页面图标
    在静态资源下放置名为favicon.ico的图标图片,打开项目后就会在页面标签上显示。
    制作ico图标:https://tool.lu/favicon/
    SpringBoot入门学习笔记(二、日志与Web开发)
    SpringBoot入门学习笔记(二、日志与Web开发)

2. 模板引擎

JSP、Velocity、Freemarker、Thymeleaf,…
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot推荐Thymeleaf,语法简单,功能强大。

1. 引入thymeleaf
<!--    引入thymeleaf模板引擎    -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. Thymeleaf的使用
@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    ...
只要把html页面放在classpath:/templates/(即resources/templates/)目录下,thymeleaf就能自动渲染。

例:
@Controller
Controller里添加:

  @RequestMapping("/success")
  public String success(){
      return "success";
  }
然后在resources/templates目录下新建一个success.html,当页面访问/success时,就会去resources/templates找返回的字符串加.html页面。

SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
Thymeleaf官网:https://www.thymeleaf.org/index.html
右键PDF,点击链接存储为,下载pdf文档。
SpringBoot入门学习笔记(二、日志与Web开发)
在html页面的html标签中导入thymeleaf的名称空间:

<html lang="en" xmlns:th="http://www.thymeleaf.org">

导入后,在使用的时候会有语法提示

修改Controller中的方法,保存数据

    //查出数据,在页面显示
   @RequestMapping("/success")
   public String success(Map<String, Object> map){
       map.put("hello", "<h1>你好</h1>");
       map.put("users", Arrays.asList("zhangsan", "lisi", "wangwu"));
       return "success";
   }

在页面中取值

    <div id="div01" class="myDiv" th:id="${hello}" th:class="${hello}" th:text="${hello}"></div>
    <hr/>
    <!--  转义特殊字符(即照原样的字符串显示)  -->
    <div th:text="${hello}"></div>
    <!--  不转义特殊字符  -->
    <div th:utext="${hello}"></div>
    <hr/>
    <!--  th:each每次遍历都会生成当前标签  -->
    <!--  写法一  -->
    <h4 th:each="user:${users}" th:text="${user}"></h4>
    <hr/>
    <!--  写法二  -->
    <h4>
        <span th:each="user:${users}">[[${user}]] </span>
    </h4>

SpringBoot入门学习笔记(二、日志与Web开发)

3. Thymeleaf的语法

内容过多,单独写在另一篇了,详情查看:https://blog.csdn.net/qq_36903261/article/details/106190445

thymeleaf标签的优先级:
SpringBoot入门学习笔记(二、日志与Web开发)
使用Thymeleaf的一个练习:https://blog.csdn.net/qq_36903261/article/details/106217848

3. 错误处理机制

SpringBoot默认的错误处理机制

默认效果:
  1. 如果是用浏览器进行访问的,SpringBoot会返回一个默认的错误页面
    SpringBoot入门学习笔记(二、日志与Web开发)
    浏览器发送请求的请求头:
    SpringBoot入门学习笔记(二、日志与Web开发)

  2. 如果是其他客户端,默认响应一个json数据的错误信息
    SpringBoot入门学习笔记(二、日志与Web开发)
    发送请求的请求头:
    SpringBoot入门学习笔记(二、日志与Web开发)

原理:

参照ErrorMvcAutoConfiguration(错误处理的自动配置)
给容器中添加了以下组件:

  1. DefaultErrorAttributes:帮我们在页面共享信息

    @Deprecated
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }
    
  2. BasicErrorController:处理默认/error请求

    @Controller
    @RequestMapping({"${server.error.path:${error.path:/error}}"})
    public class BasicErrorController extends AbstractErrorController {
    
    //产生html类型的数据,即浏览器发送的请求来到这个方法处理
    @RequestMapping(produces = {"text/html"})
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //去哪个页面作为错误页面,包含页面地址和页面内容
        ModelAndView modelAndView = this.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 = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
    
  3. ErrorPageCustomizer

    @Value("${error.path:/error}")
    private String path = "/error";	//系统出现错误以后来到error请求进行处理(类似于web.xml注册的错误页面规则)
    
  4. DefaultErrorViewResolver

        public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
            ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
            if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
                modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
            }
    
            return modelAndView;
        }
    
        private ModelAndView resolve(String viewName, Map<String, Object> model) {
        	//默认SpringBoot可以去找到一个页面(error/404)
            String errorViewName = "error/" + viewName;
            //如果模板引擎可以解析这个页面地址就用模板引擎进行解析
            TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
            //模板引擎可用就返回errorViewName指定的视图地址,如果不可用,就在静态资源目录下找errorViewName对应的页面(error/404.html)
            return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
        }
    
步骤:

一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,然后被BasicErrorController处理。
响应页面:去哪个页面由DefaultErrorViewResolver解析得到的。

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
       Iterator var5 = this.errorViewResolvers.iterator();

	//所有的errorViewResolvers得到modelAndView
       ModelAndView modelAndView;
       do {
           if (!var5.hasNext()) {
               return null;
           }

           ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
           modelAndView = resolver.resolveErrorView(request, status, model);
       } while(modelAndView == null);

       return modelAndView;
   }

定制错误响应

1. 定制错误页面
  1. 有模板引擎的情况下:error/状态码页面(templates/error)
    将错误页面命名为错误状态码.html放在模板引擎目录里的error目录下,发生错误就会找此目录下找相对应的错误页面。
    我们也可以用4xx和5xx页面作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找相对应的错误页面,没有才找xx页面)

页面能获取的信息:

  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验的所有错误
  1. 没有模板引擎的情况下:模板引擎找不到这个错误页面,就会在静态资源目录下找(templates目录下没有error),但是在静态资源目录下的页面就不能使用模板引擎语法。
  2. 以上都没有错误页面,使用默认的错误提示页面。
2. 定制错误的json数据
  1. 自定义异常处理和返回定制json数据(没有自适应效果)

    package com.angenin.springboot.controller;
    
    import com.angenin.springboot.exception.UserNotExistException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @ControllerAdvice   //异常处理器
    public class MyExceptionHandler {
    
    //1. 浏览器和其他客户端都返回json数据
    //只要出现异常SpringMVC就会调用这个方法
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String, Object> handleExcption(Exception e){
        Map<String, Object> map = new HashMap<>();
        map.put("code", "user.notexist");
        map.put("message", e.getMessage());
        return map;
    }
    

    SpringBoot入门学习笔记(二、日志与Web开发)

  2. 转发到error进行自适应效果处理

    //2. 转发到/error让BasicErrorController帮我们自适应
    @ExceptionHandler(UserNotExistException.class)
    public String handleExcption(Exception e, HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
    
        //传入我们自己的错误状态码 4xx 5xx(如果状态码不设置,转发后状态码为200)
    //        BasicErrorController中获取状态码的语句:Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
        request.setAttribute("javax.servlet.error.status_code", 400);
    
        map.put("code", "user.notexist");
        //但是把e.getMessage()改为自定义错误信息(如用户不存在),数据却没携带出去,具体看第三点
        map.put("message", e.getMessage());
        //因为BasicErrorController是处理/error的,并且区分了浏览器和其他客户端
        //因此,只要处理完后转发到/error后,BasicErrorController就会帮我们处理
        return "forward:/error";
    }
    

    SpringBoot入门学习笔记(二、日志与Web开发)

  3. 将我们定制的数据携带出去(第二点的扩展,重点)
    出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes(是AbstractErrorController(ErrorController)规定的方法)得到的,而注册BasicErrorController时,有@ConditionalOnMissingBean(value = {ErrorController.class},search = SearchStrategy.CURRENT)注解(即如果有ErrorController或其子类,就不注册BasicErrorController)。

    1. 我们可以完全编写一个ErrorController的实现类(或者编写AbstractErrorController的子类),重写需要的方法,放到容器中来代替BasicErrorController。
    2. 页面上能用的数据,或者json返回能用的数据都是通过errorAttributes.getErrorAttributes得到的。
      容器中DefaultErrorAttributes.getErrorAttributes()默认进行数据处理的。

    新建一个继承DefaultErrorAttributes的子类(新建ErrorAttributes的实现类也可以,不过需要直接写方法,比较麻烦)

    package com.angenin.springboot.component;
    
    import org.springframework.boot.web.error.ErrorAttributeOptions;
    import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.WebRequest;
    import java.util.Map;
    
    @Component  //给容器中加入我们自己定义的MyErrorAttributes
    public class MyErrorAttributes extends DefaultErrorAttributes {
    
        @Override
        public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
            //调用父类的方法,获取map
            Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
            //往map中添加数据
            errorAttributes.put("k1", "v1");
            //返回值的map就是页面和json能获取的所有字段
            return errorAttributes;
        }
    }
    

    我们就可以MyExceptionHandler设置完map后保存到request域中

      //保存到request域中
      request.setAttribute("ext", map);
    

    然后在MyErrorAttributes中获取request域中的map,进行保存

        @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        //调用父类的方法
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        errorAttributes.put("k1", "v1");
        //webRequest 继承了RequestAttributes(RequestAttributes包装了request和session)
        //getAttribute第一个参数为保存的数据的key,第二个是从哪个域中拿数据(0为request,1为session)
        //异常处理器携带的数据,因为是异常处理器是转发到/error,所以保存到request也可以拿到,如果是重定向,那么异常处理器的数据就要保存到sesssion中
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        //把获取的数据放入
        errorAttributes.put("ext", ext);
        //返回浏览器和其他客户端能读取的数据
        return errorAttributes;
    }
    

    在页面就可以用${ext.xxx}取出自己设置的信息。

    <h2>exception: [[${ext.exception}]]</h2>
    <h2>message: [[${ext.message}]]</h2>
    

    SpringBoot入门学习笔记(二、日志与Web开发)
    最终效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容。

4. SpringMVC自动配置

1.SpringMVC auto-configuration

SpringBoot对SpringMVC的默认配置:

  1. 自动注册了ContentNegotiatingViewResolverBeanNameViewResolver组件
    自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发/重定向/…))
    ContentNegotiatingViewResolver:组合了所有的视图解析器。
    我们可以往容器添加一个自己配置的视图解析器(实现了ViewResolver的类),SpringBoot会自动将其组合进来。
  2. 自动注册了ConverterGenericConverterFormatter组件
    Converter:转换器(类型转换)
    Formatter:格式化器(例:字符串转为日期类型(日期格式化的规则))
    我们添加格式化器和转换器,只需要放到容器中即可使用。
  3. 自动注册了HttpMessageConverters组件
    HttpMessageConverters:SpringMVC用来转换Http请求和响应的(User对象–>JSON

    HttpMessageConverters:是从容器中确定的,获取所有的HttpMessageConverter。
    给容器中添加HttpMessageConverter,即可使用。
  4. 自动注册了MessageCodesResolver组件。
    定义错误代码生成规则。
  5. 自动注册了ConfigurableWebBindingInitializer组件
    初始化WebDataBinder(web数据绑定器)(请求数据–>JavaBean)
    我们可以配置一个ConfigurableWebBindingInitializer来替换默认的,只需要添加到容器中。
  6. org.springframework.boot.autoconfigure.web:web的所有自动场景。

2. 扩展SpringMVC

即可以保留所有的自动配置,也能用我们扩展的配置。
编写一个配置类(@Configuration),实现WebMvcConfigurer,并且不能标注@EnableWebMvc。

package com.angenin.springboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/angenin").setViewName("success");
    }
}

SpringBoot入门学习笔记(二、日志与Web开发)
原理:

  1. WebMvcAutoConfiguration是SpringMVC的自动配置类
  2. 在做其他自动配置时会导入@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
     @Configuration(proxyBeanMethods = false)
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {...}
    
    其父类的setConfigurers方法:
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    //从容器中获取所有的WebMvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
    
  3. 容器中的所有WebMvcConfigurer都会一起起作用
  4. 我们的配置类也会被调用
    效果:SpringMVC 的自动配置和我们的扩展配置都会起作用。

3. 全面接管SpringMVC

SpringBoot对SpringMVC的自动配置不需要了,所有SpringMVC的自动配置都失效了,需要我们自己配置。
在配置类上添加@EnableWebMvc即可。

为什么加了@EnableWebMvc自动配置就全部失效了?
原理:

  1. @EnableWebMvc的核心
    @Import({DelegatingWebMvcConfiguration.class})
    public @interface EnableWebMvc {}
    
  2. DelegatingWebMvcConfiguration.class
    @Configuration(proxyBeanMethods = false)
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {...}
    
  3. WebMvcAutoConfiguration.class
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
    //容器中没有这个组件的时候,这个自动配置类才生效
    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
    @AutoConfigureOrder(-2147483638)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration {
    
  4. @EnableWebMvc将WebMvcConfigurationSupport组件添加到容器中。
  5. 添加的WebMvcConfigurationSupport组件只是SpringMVC最基本的功能。

5. 如何修改SpringBoot的默认配置

模式:

  1. SpringBoot在自动配置很多组件的时候,会先查看容器中是否有用户自己配置的组件(@Bean/@Component/…),如果有,就使用用户配置的组件,如果没有,才自动配置。如果有些组件可以有多个(如ViewResolver),SpringBoot会将用户配置的和默认配置的组合起来。
  2. 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置。
  3. 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置。

6. 配置嵌入式Servlet容器

SpringBoot默认使用Tomcat作为嵌入的Servlet容器。
SpringBoot入门学习笔记(二、日志与Web开发)

定制和修改Servlet容器相关的配置

  1. 修改和server有关的配置(ServerProperties)
    #通用的servlet容器设置 server.xxx
    server.port=8081
    server.servlet.context-path=/crud
    
    #tomcat的设置 server.tomcat.xxx
    server.tomcat.uri-encoding=utf-8
    
  2. 在@Configuration配置类中编写一个返回WebServerFactoryCustomizer的类(嵌入式的servlet容器的定制器,用于修改servlet容器的配置)
        //定制嵌入式的Servlet容器相关的规则
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
            	//修改端口
                factory.setPort(8090);
            }
        };
    }
    

两种方法同时写,第二种方法生效。

注册Servlet三大组件「Servlet、Filter、Listener」

由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
所以,注册三大组件的需要用以下的方式:

  • ServletRegistrationBean
  • FilterRegistrationBean
  • ServletListenerRegistrationBean
注册Servlet(ServletRegistrationBean)

新建一个Servlet

package com.angenin.springboot.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 {

    @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 myserlvet");
    }
}

在@Configuration配置类中

   @Bean   //注册servlet
    public ServletRegistrationBean myServlet(){
		//有参构造器,第一个参数为servlet,第二个为拦截的路径
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        return servletRegistrationBean;
    }

SpringBoot入门学习笔记(二、日志与Web开发)

注册Filter(FilterRegistrationBean)

新建一个Filter

package com.angenin.springboot.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter implements Filter {
    @Override   //filter的初始化
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override   //filter的过滤
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletResponse.getWriter().write("hello filter\n");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override   //filter的销毁
    public void destroy() {

    }
}

在@Configuration配置类中

    @Bean   //注册filter
    public FilterRegistrationBean myFilter(){
        //有参构造器,第一个参数为filter,第二个为拦截的servlet(多个)
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        //设置filter
        filterRegistrationBean.setFilter(new MyFilter());
        //设置拦截路径(多个)
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
        return filterRegistrationBean;
    }

SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)

注册Listener(ServletListenerRegistrationBean)

新建一个Listener

package com.angenin.springboot.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyListener implements ServletContextListener {

    @Override   //初始化,代表web应用启动
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized。。。web应用启动");
    }

    @Override   //销毁,代表web应用停止
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed。。。web应用停止");
    }
}

在@Configuration配置类中

    @Bean   //注册listener
    public ServletListenerRegistrationBean myListener(){
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());
        return servletListenerRegistrationBean;
    }

SpringBoot入门学习笔记(二、日志与Web开发)

SpringBoot帮我们自动配置SpringMVC的时候,自动注册SpringMVC的前端控制器(DispatcherServlet)
在DispatcherServletAutoConfiguration.class中

        @Bean(name = {"dispatcherServletRegistration"})
        @ConditionalOnBean(value = {DispatcherServlet.class}, name = {"dispatcherServlet"})
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        	//点进去DispatcherServletRegistrationBean可以发现是继承于ServletRegistrationBean
        	//dispatcherServlet为SpringMVC的前端控制器,xx.getPath()点进去发现是为path="/"(即拦截静态资源但不会拦截jsp,"/*"拦截静态资源和jsp)
        	//可以通过spring.mvc.servlet.path来修改SpringMVC前端控制器默认拦截的请求路径
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

我们可以通过spring.mvc.servlet.path来修改SpringMVC前端控制器默认拦截的请求路径。

切换Serlvet容器

SpringBoot还支持Jetty(适合长连接(如聊天))、Undertow(不支持jsp,高性能非阻塞,并发好)这两个servlet容器。

默认使用Tomcat
SpringBoot入门学习笔记(二、日志与Web开发)

切换成Jetty

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

运行程序
SpringBoot入门学习笔记(二、日志与Web开发)
同理,切换成Undertow也一样

		<!--排除tomcat-->
        <!--引入undertow-->
        <dependency>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

运行程序
SpringBoot入门学习笔记(二、日志与Web开发)

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

EmbeddedWebServerFactoryCustomizerAutoConfiguration:ServletWeb服务器工厂自动配置

@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 {

    @Bean
    @ConditionalOnClass(
        name = {"org.apache.catalina.startup.Tomcat"}
    )
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

ServletWebServerFactoryConfiguration:ServletWeb服务器工厂配置

    @Configuration(
    proxyBeanMethods = false
    )
    @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedUndertow {
        EmbeddedUndertow() {
        }

        @Bean
        UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
            UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
            factory.getDeploymentInfoCustomizers().addAll((Collection)deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getBuilderCustomizers().addAll((Collection)builderCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedJetty {
        EmbeddedJetty() {
        }

        @Bean
        JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
            JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
            factory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})//判断当前是否引入了Tomcat依赖
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )//判断当前容器是否有用户自己定义的ServletWebServerFactory(ServletWeb服务器工厂)
    static class EmbeddedTomcat {
        EmbeddedTomcat() {
        }

        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }
    }

ServletWebServerFactory(ServletWeb服务器工厂)

@FunctionalInterface
public interface ServletWebServerFactory {
	//获取web服务器
    WebServer getWebServer(ServletContextInitializer... initializers);
}

以TomcatServletWebServerFactory为例:

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
		//创建一个tomcat
        Tomcat tomcat = new Tomcat();
        //配置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);
        //返回配置好的tomcat
        return this.getTomcatWebServer(tomcat);
    }

嵌入式servlet容器启动原理

  1. SpringBoot应用启动运行run方法
  2. refreshContext(context);SpringBoot刷新IOC容器(创建IOC容器对象并初始化容器,创建容器中的每一个组件(如果是servlet就创建AnnotationConfigServletWebServerApplicationContext,否则如果是reactive就创建AnnotationConfigReactiveWebServerApplicationContext,如果都不是创建AnnotationConfigApplicationContext))
  3. refresh((ApplicationContext) context);刷新刚才创建好的IOC容器
    //刷新IOC容器
    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();servlet的IOC容器重写了onRefresh()方法
  2. servletIOC容器会创建Web服务器createWebServer();
  3. 获取ServletWeb服务器工厂ServletWebServerFactory factory = getWebServerFactory();
    从IOC容器中获取ServletWebServerFactory.class组件(即TomcatServletWebServerFactory)String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
  4. 使用容器工厂获取web服务器this.webServer = factory.getWebServer(getSelfInitializer());
  5. webServer创建对象并启动servlet容器this.tomcat.start();
    先启动嵌入式的servlet容器,再将ioc容器中剩下没创建出的对象获取出来this.finishBeanFactoryInitialization(beanFactory);

在IOC容器启动时创建嵌入式servlet容器。

使用外置的servlet容器

嵌入式servlet容器
使用:jar
优点:简单、快捷
缺点:默认不支持jsp、优化定制比较复杂

使用外置的servlet容器步骤:
新建项目,选择war包
SpringBoot入门学习笔记(二、日志与Web开发)
只选个springweb模块,创建项目
选择war包,tomcat的依赖默认是provided
SpringBoot入门学习笔记(二、日志与Web开发)
而新建的war包项目多了两个类(必须的),而且ServletInitializer必须继承SpringBootServletInitializer,并调用configure方法。
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
新建的项目是没有webapp包的
SpringBoot入门学习笔记(二、日志与Web开发)
可以手动建,也可以自动生成
SpringBoot入门学习笔记(二、日志与Web开发)
点击yes创建
SpringBoot入门学习笔记(二、日志与Web开发)
然后生成web.xml文件
SpringBoot入门学习笔记(二、日志与Web开发)
创建服务器
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
SpringBoot入门学习笔记(二、日志与Web开发)
然后点击应用确定,完成创建服务器
在webapp目录下新建一个hello.jsp页面,启动服务器
SpringBoot入门学习笔记(二、日志与Web开发)
浏览器输入:http://localhost:8080/jsp/hello.jsp访问hello页面
SpringBoot入门学习笔记(二、日志与Web开发)
和SpringMVC结合起来使用

  • 在hello.jsp中加入一个a标签<a href="abc">abc</a>
  • 在WEB-INF下新建一个success.jsp
<h1>success</h1>
<h3>${msg}</h3>
  • main/java/com/angenin/springboot下新建controller/HelloController.java
@Controller
public class HelloController {
    @GetMapping("/abc")
    public String hello(){
        model.addAttribute("msg", "你好");
        return "success";
    }
}

然后在application.properties配置文件中添加

#配置视图解析器
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

启动项目,从hello页面点击abc跳转到success页面
SpringBoot入门学习笔记(二、日志与Web开发)

原理:

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的servlet容器。
war包:启动服务器,服务器启动SpringBoot应用(关键在于SpringBootServletInitializer),启动ioc容器。

规则:
  1. 服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例。
  2. ServletContainerInitializer的实现放在jar包的META-INF/services目录下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名。
  3. 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类。
规则:
  1. 启动Tomcat
  2. 找org/springframework/spring-web/5.2.6RELEASE.jar/META-INF/services/javax.servlet.ServletContainerInitializer文件
    此文件的内容:org.springframework.web.SpringServletContainerInitializer
  3. SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>里,为这些WebApplicationInitializer类型的类创建实例。
  4. 每一个WebApplicationInitializer都调用自己的onStartup。
  5. 相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法。
    SpringBoot入门学习笔记(二、日志与Web开发)
  6. SpringBootServletInitializer实例在执行onStartup的时候会createRootApplicationContext创建容器。
    	protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
    		//1.创建SpringApplicationBuilder
    		SpringApplicationBuilder builder = createSpringApplicationBuilder();
    		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(AnnotationConfigServletWebServerApplicationContext.class);
    
    		//调用configure方法,子类重写了这个方法
    		builder = configure(builder);
    		//使用builder创建一个Spring应用
    		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
    		SpringApplication application = builder.build();
    		if (application.getAllSources().isEmpty()
    				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
    			application.addPrimarySources(Collections.singleton(getClass()));
    		}
    		Assert.state(!application.getAllSources().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.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
    		}
    		application.setRegisterShutdownHook(false);
    		
    		//启动Spring应用
    		return run(application);
    	}
    
  7. Spring的应用就启动并且创建IOC容器。
    	public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		configureHeadlessProperty();
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			Banner printedBanner = printBanner(environment);
    			context = createApplicationContext();
    			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			listeners.running(context);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		return context;
    	}
    

启动servlet容器,在启动SpringBoot应用。

学习视频(p21-p52):https://www.bilibili.com/video/BV1gW411W76m?p=21

相关标签: spring boot java