Springboot拦截器及统一异常处理

文章目录

  • 一、Java中异常相关概念
    • 1、异常类
    • 2、异常处理方法
    • 3、注意事项
    • 4、自定义异常
  • 二、配置全局异常处理
    • 1、统一返回体定义
    • 2、定义异常处理实现类
    • 3、全局异常处理类
  • 三、Springboot拦截器
    • 1、定义拦截器
    • 2、注册拦截器
  • 四、验证效果


一、Java中异常相关概念

1、异常类

  • Throwable类:Java中所有异常类的父类,它包含了最终要的两个类Exception和Error。
  • Error类:属于程序无法处理的错误,是JVM需要承担的,无法通过try-catch进行捕捉,例如系统崩溃、内存不足、堆栈溢出,编译器不会对这类异常进行检查,一旦发生就容易导致程序运行终止,仅靠程序本身无法恢复。
  • Exception:程序本身可以处理的异常,可以通过catch进行捕捉,也是我们需要处理的,以保证程序能够正常运行。

Exception又分为运行时异常(RunTimeException,又叫非受检查异常unchecked
Exception)和非运行时异常(又叫受检查异常checked Exception)。

运行时异常我们可处理可不处理,一般由程序逻辑错误引起,我们应该在编码时尽量避免这种错误,比如:NullPointException

非运行时异常时Exception中除RunTimeException以外的异常,比如:IOException、SQLException等以及我们自定义的Exception异常,这种异常,Java编译器会强制要求我们处理

@SneakyThrows注解:作用在方法上,加上以后可以对非运行时异常不进行处理

2、异常处理方法

  • try-catch:try中放可能发生异常的代码,如果发生异常,后面的代码不会再执行,直接进入catch,在catch中拿到异常对象,我们进行处理;
  • try-catch-finally:finally是无论异常是否发生都会执行的,通常用来释放资源;
  • try-finally:相当于没有捕捉异常;
  • throws:在方法名后面进行抛出,表明该方法对此异常不进行处理,由调用者进行处理,谁用谁处理,调用者也可继续向上抛出;
  • throw:在方法内进行抛出,我们手动抛出一个异常对象。

3、注意事项

  • 对于非运行时异常,程序必须进行处理,用try-catch或throws都可以,在写代码时idea会提示;
  • 对运行时异常,程序中没有处理,默认处理方法时throws;
  • 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型不能大 于父类异常的类型,可以是一样的类型或者是父类异常的子类。

4、自定义异常

自定义异常类继承Exception或RunTimeException,继承Exception属于非运行时异常,继承RunTimeException属于运行时异常。

二、配置全局异常处理

  在项目中我们通常会写很多接口,各种各样的异常出现会让我们的返回结果很受影响,因为我们的接口都会写通用的返回格式,但是异常出现时返回的错误就和我们的返回格式产生分歧,所以为了保证这种情况不出现,我们就需要配置全局异常处理,在异常发生时也按照我们想要的返回格式。

核心:@RestControllerAdvice+@ExceptionHandler

1、统一返回体定义

@Data
@NoArgsConstructor
public class R<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 成功
     */
    public static final int SUCCESS = Constants.SUCCESS;
    /**
     * 失败
     */
    public static final int FAIL = Constants.FAIL;
    /**
     * 消息状态码
     */
    private int code;
    /**
     * 消息内容
     */
    private String msg;
    /**
     * 数据对象
     */
    private T data;

    public static <T> R<T> ok() {
        return restResult(null, SUCCESS, "操作成功");
    }

    public static <T> R<T> ok(T data) {
        return restResult(data, SUCCESS, "操作成功");
    }

    public static <T> R<T> ok(String msg) {
        return restResult(null, SUCCESS, msg);
    }

    public static <T> R<T> ok(String msg, T data) {
        return restResult(data, SUCCESS, msg);
    }

    public static <T> R<T> fail() {
        return restResult(null, FAIL, "操作失败");
    }

    public static <T> R<T> fail(String msg) {
        return restResult(null, FAIL, msg);
    }

    public static <T> R<T> fail(T data) {
        return restResult(data, FAIL, "操作失败");
    }

    public static <T> R<T> fail(String msg, T data) {
        return restResult(data, FAIL, msg);
    }

    public static <T> R<T> fail(int code, String msg) {
        return restResult(null, code, msg);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static <T> R<T> warn(String msg) {
        return restResult(null, HttpStatus.WARN, msg);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static <T> R<T> warn(String msg, T data) {
        return restResult(data, HttpStatus.WARN, msg);
    }

    private static <T> R<T> restResult(T data, int code, String msg) {
        R<T> r = new R<>();
        r.setCode(code);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    public static <T> Boolean isError(R<T> ret) {
        return !isSuccess(ret);
    }

    public static <T> Boolean isSuccess(R<T> ret) {
        return R.SUCCESS == ret.getCode();
    }
}

2、定义异常处理实现类

继承自RuntimeException类,只传进来一个状态码,如果想传更多的参数,就自己加上,代码中有示例。

@Data
public class MessageException extends RuntimeException{

    private int code;
    //private String desc;

    public MessageException(String message,int code/**,String desc*/) {
        super(message);
        this.code = code;
        //this.desc = desc;
    }
}

3、全局异常处理类

这里可以使用@RestControllerAdvice+@ExceptionHandler或者@ControllerAdvice+@ExceptionHandler+@ResponseBody,都是可以的,@RestControllerAdvice=@ControllerAdvice+@ResponseBody。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MessageException.class)
    public R businessExceptionHandler(MessageException e){
        //log.error("MessageException "+e.getMessage(),e);
        return R.fail(e.getCode(),e.getMessage());
    }
}

三、Springboot拦截器

拦截器可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能。

1、定义拦截器

在 Spring Boot中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口,重写以下三个方法。直接上代码。

public class MessageAccessInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行前 (Controller方法调用之前)
     * 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作
     * 返回 true 表示继续向下执行,返回 false 表示中断后续操作
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    	//注意这个businessKey,后面验证的时候要使用
        String businessKey = request.getHeader("businessKey");
        if (ObjectUtil.isEmpty(businessKey)) {
        	//直接使用上文中定义的异常处理方法
            throw new MessageException("业务签名未找到!", HttpStatus.FORBIDDEN);
        } 
        return true;
    }

    /**
     * 目标方法执行后
     * 该方法在控制器处理请求方法调用之后、解析视图之前执行
     * 可以通过此方法对请求域中的模型和视图做进一步修改
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView
        modelAndView) {
        //System.out.println("postHandle: " + request.getRequestURI());
    }

    /**
     * 页面渲染后
     * 该方法在视图渲染结束后执行
     * 可以通过此方法实现资源清理、记录日志信息等工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception
        ex) {
        //System.out.println("afterCompletion: " + request.getRequestURI());
    }

2、注册拦截器

创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //addPathPatterns为拦截此请求路径的请求
        //excludePathPatterns为不拦截此路径的请求
        registry.addInterceptor(new MessageAccessInterceptor())
   			//可以设置多个路径拦截
           .addPathPatterns("/push/sms");
           //.excludePathPatterns();
    }
}

四、验证效果

如果header参数不传businessKey参数,提示信息正如咱上文中配置的那样。
Springboot拦截器及统一异常处理_第1张图片
如果header传输了businessKey参数,则会顺利通过拦截器。
成功的验证了本文的所有功能。如需源码,请评论区留下邮箱地址。
完毕!

你可能感兴趣的:(spring,spring,boot,后端,java)