⌛ 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" ;
     }
相关推荐
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭5 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫22 分钟前
泛型(2)
java
南宫生31 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石39 分钟前
12/21java基础
java
李小白661 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp1 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶2 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗2 小时前
常用类晨考day15
java
骇客野人2 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
AskHarries2 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端