⌛ SpringMVC 的声明式异常处理和拦截器等

1. 声明式异常处理

  1. 背景:编程式异常处理导致在业务代码中掺杂着大量的try-catch代码。并且代码冗余度极高。

  2. 声明式异常处理的步骤:

    ① 新建一个类,使用 @RestControllerAdvice 修饰

    ② 新建多个方法,每一个方法专门处理某一类异常信息,使用 @ExceptionHandler 修饰

java 复制代码
//@ControllerAdvice  返回的是逻辑视图
@RestControllerAdvice //返回的是(json)字符串
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public String handleRuntimeException(RuntimeException e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }

    @ExceptionHandler(SQLException.class)
    public String handleSQLException(SQLException e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }
}

2. 拦截器 - Interceptor

  1. 和过滤器的区别: 过滤器需要依赖于javaee环境;

  2. 基本使用: ① 新建类,实现 HandlerInterceptor接口

    ② 实现其中的三个方法: preHandle() , postHandle() , afterCompletion()

    ③ 配置(注册)拦截器,拦截所有

java 复制代码
//@ControllerAdvice  返回的是逻辑视图
@RestControllerAdvice //返回的是(json)字符串
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public String handleRuntimeException(RuntimeException e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }

    @ExceptionHandler(SQLException.class)
    public String handleSQLException(SQLException e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
        return e.getMessage() ;
    }
}
xml 复制代码
<context:component-scan base-package="com.bottom"/>
<!-- 注册拦截器拦截所有 -->
<mvc:interceptors>
    <bean class="com.atguigu.springmvc.interceptor.FirstInterceptor"/>
</mvc:interceptors>
  1. 拦截器的拦截规则:

① 拦截所有:

mvc:interceptors </mvc:interceptors>

② 设置拦截路径:

xml 复制代码
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/hello01/*"/>       拦截hello01下面的所有的请求路径(单层)
        <mvc:mapping path="/hello01/**"/>      拦截hello01下面的所有的请求路径(多层)
        <mvc:exclude-mapping path="/hello01/h03"/>  排除指定规则的路径
        <bean class="com.bottom.springmvc.interceptor.FirstInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  1. 拦截器栈 ① 配置:
xml 复制代码
    <mvc:interceptors>
        <bean class="com.bottom.springmvc.interceptor.InterceptorA"/>
        <bean class="com.bottom.springmvc.interceptor.InterceptorB"/>
        <bean class="com.bottom.springmvc.interceptor.InterceptorC"/>
    </mvc:interceptors>

建立 InterceptorA类

java 复制代码
public class InterceptorA implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("A - preHandle ... ");
        return true ;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("A - postHandler ... ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("A - afterCompletion ... ");
    }
}

建立 InterceptorB类

java 复制代码
public class InterceptorB implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("B - preHandle ... ");
        return true ;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("B - postHandler ... ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("B - afterCompletion ... ");
    }
}

建立InterceptorC类

java 复制代码
public class InterceptorC implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("C - preHandle ... ");
        return true ;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("C - postHandler ... ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("C - afterCompletion ... ");
    }
}

② 执行顺序:

  • A - preHandle ...

  • B - preHandle ...

  • C - preHandle ...

  • Hello01 - h02()...

  • C - postHandler ...

  • B - postHandler ...

  • A - postHandler ...

  • C - afterCompletion ...

  • B - afterCompletion ...

  • A - afterCompletion ...

  1. 拦截器栈相关的源码阅读

① 阅读DispatcherServlet的doDispatch方法:

java 复制代码
   protected void doDispatch(){
      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        //如果这里成立,则第1081行就不执行了
        return;
      }

      //它是Hello01Controller的h02方法的调用
      mv = ha.handle(request,response); //此处是第1081行

   }

② 阅读mappedHandler.applyPreHandle():

java 复制代码
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
		return true;
	}

通过上述代码的得出结论:

  • 如果preHandle没有放行,则执行afterCompletion(跳过postHandle)

  • preHandle是正序执行的

③ 再次回到doDispatch方法:

java 复制代码
   protected void doDispatch(){
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          //如果这里成立,则第1081行就不执行了
          return;
        }

        //它是Hello01Controller的h02方法的调用
        mv = ha.handle(request,response); //此处是第1081行

        //1088行
        //这句代码的作用是调用当前拦截器链的所有postHandle方法
        mappedHandler.applyPostHandle();
   }

④ 查看mappedHandler.applyPostHandle():

java 复制代码
   void applyPostHandle(){
   		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
   			HandlerInterceptor interceptor = this.interceptorList.get(i);
   			interceptor.postHandle(request, response, this.handler, mv);
   		}
   }

通过上述代码得知:拦截器链的postHandle方法是倒序执行的

⑤ 再次回到doDispatch方法:

java 复制代码
   protected void doDispatch(){
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          //如果这里成立,则第1081行就不执行了
          return;
        }

        //它是Hello01Controller的h02方法的调用
        mv = ha.handle(request,response); //此处是第1081行

        //1088行
        //这句代码的作用是调用当前拦截器链的所有postHandle方法
        mappedHandler.applyPostHandle();

        //1098行
        processDispatchResult();   //处理分发结果
   }

查看processDispatchResult():

java 复制代码
   private void processDispatchResult(){
        if (mappedHandler != null) {
   			// Exception (if any) is already handled..
   			mappedHandler.triggerAfterCompletion(request, response, null);
   		}
   }

查看mappedHandler.triggerAfterCompletion()方法:

java 复制代码
   void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
   		for (int i = this.interceptorIndex; i >= 0; i--) {
   			HandlerInterceptor interceptor = this.interceptorList.get(i);
   			interceptor.afterCompletion(request, response, this.handler, ex);
   		}
   }

通过上述这个方法的代码得知:拦截器链的afterCompletion的调用也是倒序的

3. 数据校验

  1. 为什么需要数据校验?为什么需要双校验?

    在数据操作之前,需要保证数据的合理性,所以需要数据校验;

    双校验指的是客户端和服务器端都要进行数据校验。特殊的场景下,客户端会使用一些程序伪装成浏览器请求,这样的话客户端校验就绕开了,因此服务器端也必须校验。

  2. 服务器端如何进行数据校验

    • 添加依赖: hibernate-validator
xml 复制代码
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>6.2.5.Final</version>
</dependency>
  • 在entity上使用JSR303相关的校验注解
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @NotNull
    private Integer id ;
    @Length(min = 5 , max = 20)
    private String name ;
    @Min(18)
    @Max(99)
    private Integer age ;
    @Email
    private String email ;
}
  • 在控制器方法上使用@Validated进行数据校验。如果校验有问题,则错误信息会存放在BindingResult对象中
java 复制代码
     public Object add(@Validated User user , BindingResult bindingResult){
         System.out.println("user = " + user);
         if(bindingResult.hasErrors()){
             //如果数据校验有错误,则错误信息会存放在bindingResult对象中
             return bindingResult.getFieldError().toString();
         }
         return "succ" ;
     }
相关推荐
程序员爱钓鱼11 分钟前
Go语言实战案例:简易JSON数据返回
后端·go·trae
程序员爱钓鱼18 分钟前
Go语言实战案例:用net/http构建一个RESTful API
后端·go·trae
bobz96521 分钟前
firewalld 添加 nat 转发
后端
摇滚侠23 分钟前
Oracle 关闭 impdp任务
java
编程爱好者熊浪1 小时前
RedisBloom使用
java
苇柠1 小时前
Spring框架基础(1)
java·后端·spring
yics.1 小时前
数据结构——栈和队列
java·数据结构
架构师沉默2 小时前
我用一个 Postgres 实现一整套后端架构!
java·spring boot·程序人生·架构·tdd
xiucai_cs2 小时前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
向阳花自开2 小时前
Spring Boot 常用注解速查表
java·spring boot·后端