⌛ 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" ;
     }
相关推荐
Мартин.3 分钟前
[Meachines] [Easy] Help HelpDeskZ-SQLI+NODE.JS-GraphQL未授权访问+Kernel<4.4.0权限提升
后端·node.js·graphql
Yeats_Liao18 分钟前
Spring 框架:配置缓存管理器、注解参数与过期时间
java·spring·缓存
Yeats_Liao18 分钟前
Spring 定时任务:@Scheduled 注解四大参数解析
android·java·spring
码明18 分钟前
SpringBoot整合ssm——图书管理系统
java·spring boot·spring
某风吾起23 分钟前
Linux 消息队列的使用方法
java·linux·运维
xiao-xiang26 分钟前
jenkins-k8s pod方式动态生成slave节点
java·kubernetes·jenkins
网络风云27 分钟前
golang中的包管理-下--详解
开发语言·后端·golang
取址执行37 分钟前
Redis发布订阅
java·redis·bootstrap
S-X-S1 小时前
集成Sleuth实现链路追踪
java·开发语言·链路追踪
快乐就好ya1 小时前
xxl-job分布式定时任务
java·分布式·spring cloud·springboot