1、什么是springboot,谈谈你对springboot的理解

SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring众多框架中所需的大量且繁琐的配置文件,所以 SpringBoot是一个服务于框架的框架,服务范围是简化配置文件
让文件配置变的相当简单,让应用部署变的简单(SpringBoot内置服务器,并装备启动类代码),可以快速开启一个Web容器进行开发。

  • 可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。
  • 内嵌的Servlet容器:Spring Boot可以选择内嵌Tomcat、Jetty或者Undertow,无须以war包形式部署项目。
  • 简化的Maven配置:Springboot starter自动依赖与版本控制。
  • 自动配置Spring:Spring Boot会根据项目依赖来自动配置Spring 框架,极大地减少项目要使用的配置。
  • 提供生产就绪型功能:提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。
  • 无代码生成和xml配置:Spring Boot不生成代码。完全不需要任何xml配置即可实现Spring的所有配置。

    2、spring和springboot有什么区别

  • Springboot是基于spring框架的框架。

  • 由于spring框架开发过程中重复且繁琐的配置,衍生出了springboot框架。
  • SpringBoot就是把spring常用的插件做了一次整合,让配置全部可以自动完成。
  • Springboot starter自动依赖与版本控制。
  • springboot内嵌tomcat之流,spring没有。
  • 本质区别就是打包了Spring常用功能。

    3、Spring Boot 配置加载顺序?

    在 Spring Boot 里面,可以使用以下几种方式来加载配置。
  1. properties文件;
  2. YAML文件;
  3. 系统环境变量;
  4. 命令行参数;

优先级:
当三种文件路径相同时,三个文件中的配置信息都会生效,但是当三个文件中有配置信息冲突时,加载顺序是yml>yaml>properties
这里的逻辑顺序是先加载yml再加载yaml再加properties,后加载的自然会把先加载的数据给覆盖掉.
image.png

4、自动配置原理@EnableAutoConfiguration

默认在程序主类上加的@SpringBootAppliaction,等于下面三个注解的组合

4.1、@SpringBootConfiguration

@Configuration。代表当前是一个配置类

4.2、@ComponentScan

指定扫描哪些,Spring注解;

4.3、@EnableAutoConfiguration

这个注解就是Springboot自动配置的核心注解

4.3.1、@AutoConfigurationPackage

指定了默认的包规则

4.3.2、@Import(AutoConfigurationImportSelector.class)

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
在这个过程中会把Springboot127个场景的自动配置全部加载,但是通过@Conditional注解会按需配置
总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —-> 组件 —-> xxxxProperties里面拿值 ——> application.properties

5、启动过程

SpringBoot的启动过程主要分为两个大步骤
image.png

1、创建SpringApplication

  • 首先保存一些信息
  • 判断应用是什么类型REACTIVE OR SERVLET
  • 初始化启动引导器bootstrappers
  • 初始化ApplicationContextInitializer
  • 初始化应用监听器ApplicationListener

    2、启动SpringAppliaction

  • 开启StopWatch计时器,记录应用启动的时间

  • 通过createBootstrapContext(),创建应用的引导上下文,获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
  • 设置headless模式(自力更生)
  • 创建所有 Spring 运行监听器(RunListener)并发布应用启动事件,通过starting遍历通知所有组件项目正在启动
  • ApplicationArguments保存命令行参数(args参数)
  • 通过prepareEnvironment( )准备环境
  • 打印Banner信息
  • 创建IOC容器createApplicationContext( )(根据当前应用类型创建)
  • 准备IOC容器的基本信息prepareContext( )
  • 刷新IOC容器refreshContext( )
  • 处理刷新IOC容器后的事件
  • 停止StopWatch计时器
  • 所有监听器调用listeners.started(context); 通知所有的监听器 started
  • 执行所有 Runner 运行器
  • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
  • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed
  • 返回IOC容器
  1. //参考
  2. public ConfigurableApplicationContext run(String... args) {
  3. // 1.创建并启动计时监控类
  4. StopWatch stopWatch = new StopWatch();
  5. stopWatch.start();
  6. // 2.声明应用上下文对象和异常报告集合
  7. ConfigurableApplicationContext context = null;
  8. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  9. // 3.设置系统属性 headless 的值
  10. this.configureHeadlessProperty();
  11. // 4.创建所有 Spring 运行监听器并发布应用启动事件
  12. SpringApplicationRunListeners listeners = this.getRunListeners(args);
  13. listeners.starting();
  14. Collection exceptionReporters;
  15. try {
  16. // 5.处理 args 参数
  17. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  18. // 6.准备环境
  19. ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
  20. this.configureIgnoreBeanInfo(environment);
  21. // 7.创建 Banner 的打印类
  22. Banner printedBanner = this.printBanner(environment);
  23. // 8.创建应用上下文
  24. context = this.createApplicationContext();
  25. // 9.实例化异常报告器
  26. exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
  27. // 10.准备应用上下文
  28. this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  29. // 11.刷新应用上下文
  30. this.refreshContext(context);
  31. // 12.应用上下文刷新之后的事件的处理
  32. this.afterRefresh(context, applicationArguments);
  33. // 13.停止计时监控类
  34. stopWatch.stop();
  35. // 14.输出日志记录执行主类名、时间信息
  36. if (this.logStartupInfo) {
  37. (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
  38. }
  39. // 15.发布应用上下文启动完成事件
  40. listeners.started(context);
  41. // 16.执行所有 Runner 运行器
  42. this.callRunners(context, applicationArguments);
  43. } catch (Throwable var10) {
  44. this.handleRunFailure(context, var10, exceptionReporters, listeners);
  45. throw new IllegalStateException(var10);
  46. }
  47. try {
  48. // 17.发布应用上下文就绪事件
  49. listeners.running(context);
  50. // 18.返回应用上下文对象
  51. return context;
  52. } catch (Throwable var9) {
  53. this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
  54. throw new IllegalStateException(var9);
  55. }
  56. }

6、JSR303数据校验异常

真正做业务逻辑时,前端传回来的表单除了前端自己需要校验之外,后端也需要校验。原因是以防有人知道接口信息,直接通过类似postmen工具跳过前端校验直接向后端发送错误数据。而后端校验一般采用JSR303校验
校验步骤:

  1. 使用校验注解
    1. @NotNull 该属性不能为null
    2. @NotEmpty 该字段不能为null或””
    3. @NotBlank
    4. @Min
    5. @Pattern
    6. 。。。
  2. 在接口参数处添加@Valid注解开启校验功能

局部校验异常处理
此时如果想要感觉校验的具体错误结果,需要在开启@Valid注解参数的后面紧跟上BindingResult result 参数。通过这个参数可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。

6.1、分组校验

如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。

  1. 创建分组接口Insert.class Update.class
  2. 在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = “用户姓名不能为空”,groups = {Insert.class,Update.class})
  3. controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)
  4. 业务方法参数上使用@Validated注解

没有分组的校验注解不生效,除非请求上没有添加@Validated注解

6.2、自定义校验注解

  1. 自定义校验注解,必须有三个属性

    1. message()错误信息
    2. groups()分组校验
    3. payload()自定义负载信息

      // 自定义注解
      @Documented
      @Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器
      @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注
      @Retention(RUNTIME)
      public @interface ListValue {
      // 使用该属性去Validation.properties中取
      String message() default "{com.atguigu.common.valid.ListValue.message}";
      
      Class<?>[] groups() default { };
      
      Class<? extends Payload>[] payload() default { };
      
      // 数组,需要用户自己指定
      int[] value() default {};
      }
      
  2. 自定义校验器

    1. 实现ConstraintValidator接口

      public class ListValueConstraintValidator 
      implements ConstraintValidator<ListValue,Integer> { //<注解,校验值类型>
      
      // 存储所有可能的值
      private Set<Integer> set=new HashSet<>();
      
      @Override // 初始化,你可以获取注解上的内容并进行处理
      public void initialize(ListValue constraintAnnotation) {
        // 获取后端写好的限制 // 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1})
        int[] value = constraintAnnotation.value();
        for (int i : value) {
            set.add(i);
        }
      }
      
      @Override // 覆写验证逻辑
      public boolean isValid(Integer value, ConstraintValidatorContext context) {
        // 看是否在限制的值里
        return  set.contains(value);
      }
      }
      
  3. 关联校验器和注解

    7、统一异常处理

    如果每个代码块都需要手动的进行局部处理异常,会使得代码特别繁琐。此时进行统一异常处理就非常有必要。
    当代码加入了 @ControllerAdvice,则不需要必须在同一个 controller 中了。这也是 Spring 3.2 带来的新特性。从名字上可以看出大体意思是控制器增强。 也就是说,@controlleradvice + @ ExceptionHandler 也可以实现全局的异常捕捉。
    首先抽取一个异常处理类

  • @ControllerAdvice标注在类上,通过“basePackages”能够说明处理哪些路径下的异常。
  • @ExceptionHandler(value = 异常类型.class)标注在方法上 ```java @Slf4j @RestControllerAdvice(basePackages = “com.atguigu.gulimall.product.controller”)//管理的controller public class GulimallExceptionControllerAdvice {

    //某个异常处理 @ExceptionHandler(value = MethodArgumentNotValidException.class) // 也可以返回ModelAndView public R handleValidException(MethodArgumentNotValidException exception){

      Map<String,String> map=new HashMap<>();
      // 获取数据校验的错误结果
      BindingResult bindingResult = exception.getBindingResult();
      // 处理错误
      bindingResult.getFieldErrors().forEach(fieldError -> {
          String message = fieldError.getDefaultMessage();
          String field = fieldError.getField();
          map.put(field,message);
      });
    
      log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
    
      return R.error(400,"数据校验出现问题").put("data",map);
    

    } //未捕捉到的所有异常处理 @ExceptionHandler(value = Throwable.class)//异常的范围更大 public R handleException(Throwable throwable){

      log.error("未知异常{},异常类型{}",
            throwable.getMessage(),
            throwable.getClass());
    

    return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),

                 BizCodeEnum.UNKNOW_EXEPTION.getMsg());
    

    } }

因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件ValidationMessages.properties来自定义校验错误提示信息
<a name="oLHcl"></a>
## 7.1、使用枚举全局定义错误状态码
```java
/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
public enum BizCodeEnum {

    UNKNOW_EXEPTION(10000,"系统未知异常"),

    VALID_EXCEPTION( 10001,"参数格式校验失败");

    private int code;
    private String msg;

    BizCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}