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 里面,可以使用以下几种方式来加载配置。
- properties文件;
- YAML文件;
- 系统环境变量;
- 命令行参数;
优先级:
当三种文件路径相同时,三个文件中的配置信息都会生效,但是当三个文件中有配置信息冲突时,加载顺序是yml>yaml>properties
这里的逻辑顺序是先加载yml再加载yaml再加properties,后加载的自然会把先加载的数据给覆盖掉.
4、自动配置原理@EnableAutoConfiguration
默认在程序主类上加的@SpringBootAppliaction,等于下面三个注解的组合
4.1、@SpringBootConfiguration
4.2、@ComponentScan
4.3、@EnableAutoConfiguration
4.3.1、@AutoConfigurationPackage
4.3.2、@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List
3、利用工厂加载 Map
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、启动过程
1、创建SpringApplication
- 首先保存一些信息
- 判断应用是什么类型REACTIVE OR SERVLET
- 初始化启动引导器bootstrappers
- 初始化ApplicationContextInitializer
-
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容器
//参考
public ConfigurableApplicationContext run(String... args) {
// 1.创建并启动计时监控类
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2.声明应用上下文对象和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
// 3.设置系统属性 headless 的值
this.configureHeadlessProperty();
// 4.创建所有 Spring 运行监听器并发布应用启动事件
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
// 5.处理 args 参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 6.准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 7.创建 Banner 的打印类
Banner printedBanner = this.printBanner(environment);
// 8.创建应用上下文
context = this.createApplicationContext();
// 9.实例化异常报告器
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 10.准备应用上下文
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 11.刷新应用上下文
this.refreshContext(context);
// 12.应用上下文刷新之后的事件的处理
this.afterRefresh(context, applicationArguments);
// 13.停止计时监控类
stopWatch.stop();
// 14.输出日志记录执行主类名、时间信息
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 15.发布应用上下文启动完成事件
listeners.started(context);
// 16.执行所有 Runner 运行器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
// 17.发布应用上下文就绪事件
listeners.running(context);
// 18.返回应用上下文对象
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
6、JSR303数据校验异常
真正做业务逻辑时,前端传回来的表单除了前端自己需要校验之外,后端也需要校验。原因是以防有人知道接口信息,直接通过类似postmen工具跳过前端校验直接向后端发送错误数据。而后端校验一般采用JSR303校验
校验步骤:
- 使用校验注解
- @NotNull 该属性不能为null
- @NotEmpty 该字段不能为null或””
- @NotBlank
- @Min
- @Pattern
- 。。。
- 在接口参数处添加@Valid注解开启校验功能
局部校验异常处理
此时如果想要感觉校验的具体错误结果,需要在开启@Valid注解参数的后面紧跟上BindingResult result 参数。通过这个参数可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
6.1、分组校验
如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。
- 创建分组接口Insert.class Update.class
- 在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = “用户姓名不能为空”,groups = {Insert.class,Update.class})
- controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)
- 业务方法参数上使用@Validated注解
没有分组的校验注解不生效,除非请求上没有添加@Validated注解
6.2、自定义校验注解
自定义校验注解,必须有三个属性
- message()错误信息
- groups()分组校验
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 {}; }
自定义校验器
实现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); } }
-
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;
}
}