全局异常处理机制

对于异常的处理,一般分为两种方式:

  • 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
  • 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws@ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展

声明异常处理控制器类

声明异常处理控制器类

/**
 * projectName: com.atguigu.execptionhandler
 * 
 * description: 全局异常处理器,内部可以定义异常处理Handler!
 */

/**
 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
 * @ControllerAdvice 代表当前类的异常处理controller! 
 */
@RestControllerAdvice
public class GlobalExceptionHandler {


}

声明异常处理hander方法

异常处理handler方法要映射异常,发生对应的异常会调用

普通的handler方法要使用@RequestMapping注解映射路径,发生对应的路径调用

/**
 * 异常处理handler 
 * @ExceptionHandler(HttpMessageNotReadableException.class) 
 * 该注解标记异常处理Handler,并且指定发生异常调用该方法!
 * 
 * 
 * @param e 获取异常对象!
 * @return 返回handler处理结果!
 */
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){

  return null;
}

/**
 * 当发生空指针异常会触发此方法!
 * @param e
 * @return
 */
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

  return null;
}

/**
 * 所有异常都会触发此方法!但是如果有具体的异常处理Handler! 
 * 具体异常处理Handler优先级更高!
 * 例如: 发生NullPointerException异常!
 *       会触发handlerNullException方法,不会触发handlerException方法!
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){

  return null;
}

配置文件扫描控制器类配置

确保异常处理控制类被扫描

 <!-- 扫描controller对应的包,将handler加入到ioc-->
 @ComponentScan(basePackages = {"com.example.controller",
 "com.example.exceptionhandler"})

拦截器

在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测

拦截器 Springmvc VS 过滤器 javaWeb:

  • 相似点
    • 拦截:必须先把请求拦住,才能执行后续操作
    • 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
    • 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
  • 不同点
    • 工作平台不同
      • 过滤器工作在 Servlet 容器中
      • 拦截器工作在 SpringMVC 的基础上
    • 拦截的范围
      • 过滤器:能够拦截到的最大范围是整个 Web 应用
      • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
    • IOC 容器支持
      • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
      • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器

创建拦截器类

public class Process01Interceptor implements HandlerInterceptor {

  // if( ! preHandler()){return;}
  // 在处理请求的目标 handler 方法前执行
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
    System.out.println("Process01Interceptor.preHandle");

    // 返回true:放行
    // 返回false:不放行
    return true;
  }

  // 在目标 handler 方法之后,handler报错不执行!
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
    System.out.println("Process01Interceptor.postHandle");
  }

  // 渲染视图之后执行(最后),一定执行!
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
    System.out.println("Process01Interceptor.afterCompletion");
  }
}

spring mvc 配置文件添加拦截器配置

@EnableWebMvc 
@Configuration
@ComponentScan(basePackages = {"com.example.controller","com.example.exceptionhandler"}) 
public class SpringMvcConfig implements WebMvcConfigurer {
   //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) { 
        //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
        registry.addInterceptor(new Process01Interceptor());
    }
}

拦截范围

默认拦截全部

@Override
public void addInterceptors(InterceptorRegistry registry) {
    //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
    registry.addInterceptor(new Process01Interceptor());
}

精确匹配拦截

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
    registry.addInterceptor(new Process01Interceptor());
    
    //精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
    //addPathPatterns("/common/request/one") 添加拦截路径
    //也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
    registry
      .addInterceptor(new Process01Interceptor())
      .addPathPatterns("/common/request/one","/common/request/tow");
}

排除匹配拦截

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {

  //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
  registry.addInterceptor(new Process01Interceptor());

  //精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
  //addPathPatterns("/common/request/one") 添加拦截路径
  registry
    .addInterceptor(new Process01Interceptor())
    .addPathPatterns("/common/request/one","/common/request/tow");


  //排除匹配,排除应该在匹配的范围内排除
  //addPathPatterns("/common/request/one") 添加拦截路径
  //excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内
  registry
    .addInterceptor(new Process01Interceptor())
    .addPathPatterns("/common/request/one","/common/request/tow")
    .excludePathPatterns("/common/request/tow");
}

多个拦截器执行顺序

  1. preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
  2. postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
  3. afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。

参数校验

校验概述

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证

注解 规则
@Null 标注值必须为 null
@NotNull 标注值不可为 null
@AssertTrue 标注值必须为 true
@AssertFalse 标注值必须为 false
@Min(value) 标注值必须大于或等于 value
@Max(value) 标注值必须小于或等于 value
@DecimalMin(value) 标注值必须大于或等于 value
@DecimalMax(value) 标注值必须小于或等于 value
@Size(max,min) 标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction) 标注值值必须是一个数字,且必须在可接受的范围内
@Past 标注值只能用于日期型,且必须是过去的日期
@Future 标注值只能用于日期型,且必须是将来的日期
@Pattern(value) 标注值必须符合指定的正则表达式
@Email 标注值必须是格式正确的 Email 地址
@Length 标注值字符串大小必须在指定的范围内
@NotEmpty 标注值字符串不能是空字符串
@Range 标注值必须在指定的范围内

Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架

Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作

在SpringMVC 中,可直接通过注解驱动 @EnableWebMvc 的方式进行数据校验

Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中

Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下

配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作

使用参数校验注解

导入依赖

<!-- 校验注解 -->
<dependency>
  <groupId>jakarta.platform</groupId>
  <artifactId>jakarta.jakartaee-web-api</artifactId>
  <version>9.1.0</version>
  <scope>provided</scope>
</dependency>

<!-- 校验注解实现-->        
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator-annotation-processor</artifactId>
  <version>8.0.0.Final</version>
</dependency>

在实体类上应用注解

public class User {
  //age   1 <=  age < = 150
  @Min(10)
  private int age;

  //name 3 <= name.length <= 6
  @Length(min = 3,max = 10)
  private String name;

  //email 邮箱格式
  @Email
  private String email;
  
	// ...get/set
}

方法上标记和绑定错误收集

@RestController
@RequestMapping("user")
public class UserController {

  /**
     * @Validated 代表应用校验注解! 必须添加!
     */
  @PostMapping("save")
  public Object save(
    @Validated @RequestBody User user,
    //在实体类参数和 BindingResult 之间不能有任何其他参数, 
    // BindingResult可以接受错误信息,避免信息抛出!
    BindingResult result){
    //判断是否有信息绑定错误! 有可以自行处理!
    if (result.hasErrors()){
      System.out.println("错误");
      String errorMsg = result.getFieldError().toString();
      return errorMsg;
    }
    //没有,正常处理业务即可
    System.out.println("正常");
    return user;
  }
}

易混总结

@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

  1. @NotNull (包装类型不为null)

    @NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解

  2. @NotEmpty (集合类型长度大于0)

    @NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败

  3. @NotBlank (字符串,不为null,切不为" "字符串)

    @NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
    总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验


参考资料