06全局异常处理机制+拦截器+参数校验

发布时间:2024年01月17日

1. 全局异常处理机制

1.1 两种异常处理方式

开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。

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

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

站在宏观角度来看待声明式事务处理:

整个项目从架构这个层面设计的异常处理的统一机制和规范。

一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。

使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!

1.2 基于注解的异常处理

处理流程:

发生异常 --> 去往ControllerAdvice注解的类 --> 寻找@ExceptionHandler(绑定的类名.class)注解的异常处理类

  1. 声明异常处理控制器类

    异常处理控制类,统一定义异常处理handler方法!

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

/**
 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
}
  1. 在异常处理控制器类中声明异常处理handler方法

异常处理handler方法和普通的handler方法参数接收和响应都一致!

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

/**
 * 当发生空指针异常会触发此方法!
 * 这是具体异常处理Handler,如果所有具体异常处理handler都不匹配,就走@ExceptionHandler(Exception.class)
 */
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

    return null;
}

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

    return null;
}

记得将异常处理控制类所在的包加入包扫描.

2. 拦截器

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

什么时候拦截器会生效?
调用handler之前(preHandler)和之后(postHandler),DS返回结果给前端之前(afterCompletion)
拦截器图1

Springmvc中的拦截器 VS javaWeb中的过滤器:

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

总结 : 拦截器是对过滤器的进一步升级 , 能用拦截器就没必要用过滤器
拦截器图2


拦截器使用

  1. 创建拦截器类 , 实现HandlerInterceptor:
package com.sunsplanter.interceptors

public class MyInterceptor implements HandlerInterceptor {

    // if( ! preHandler()){return;}
    //preHandle在处理请求的目标 handler 方法前执行
    //根据返回值true/false决定是否放行

/**
 *request 请求
 *response响应
 *handler 处理之前要判断是否拦截的handler方法
 *modelAndView 返回的视图和共享域数据对象
 */
    @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");
         
        return true;
    }
 
    // postHandle在目标 handler 方法后执行,若handler报错则该postHandler不执行!
    @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");
    }
 
    // afterCompletion在渲染视图之后执行(最后),一定执行!
    @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("MyInterceptor.afterCompletion");
    }
}
  1. 在config配置类中添加(注册)第一步中编写的各个拦截器
@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.sunsplanter.controller","com.sunsplanter.interceptors"}) 
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }

    //开启静态资源处理 <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) { 
        //将MyInterceptor中的所有拦截器都添加到Springmvc环境,默认拦截所有Springmvc分发的请求
        registry.addInterceptor(new MyInterceptor());
    }
}
  1. 拦截器的进一步配置

a. 默认拦截全部(即上述)

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

b . 精准配置

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

c. 排除配置

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    //排除匹配,排除已在匹配的范围的handler
    //addPathPatterns("/user/**") 添加拦截路径
    //excludePathPatterns("/user/data"); 排除路径,排除应该在拦截的范围内
    registry.addInterceptor(new Process01Interceptor())
            .addPathPatterns("/user/**")
            .excludePathPatterns("/user/data");
}
  1. 多个拦截器的执行顺序

SpringMVC 会把所有拦截器收集到一起 , 然后:

a. 按照配置顺序调用各个 preHandle() 方法。
b. 按照配置逆序调用各个 postHandle() 方法。
c. 按照配置逆序调用各个 afterCompletion() 方法。


3. 参数校验

什么是参数校验?

在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

一切的开始是导入依赖:
使用参数校验的前提是同时开启@EnableWebMvc和添加依赖

<!-- 校验注解 -->
<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>

步骤是 :

  1. 在实体类(Bean)属性中用用注解限定属性的取值范围
  2. 在handler中标记和绑定错误收集
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;

/**
 * projectName: com.sunsplanter.pojo
 */
public class User {
    //age   1 <=  age < = 150
    @Min(1)
    private int age;

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

    //email 邮箱格式
    @Email
    private String email;

    public int getAge() {
        return age;
    }
}
@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;
    }
}

常用的校验注解就是非空和长度限定 , 其中存在三种非空 :

  1. @NotNull : 包装类型不为null
  2. @NotEmpty : 集合类型长度大于0 , 如Collection/Map/数组对象
  3. @NotBlank : 校验字符串,确保字符串不为null/或只包含空格
文章来源:https://blog.csdn.net/m0_46671240/article/details/135614275
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。