Spring MVC统一异常处理

Spring MVC统一异常处理

一、为什么需要统一异常处理?

1.1 try catch带来的问题

  • 平时项目中处理Controller层异常很多时候都会使用try catch去捕获异常(包括Controller层中调用的Service层中的try catch)。

  • 这样处理异常,会造成代码中带有大量的try catch块,不但代码不美观不优雅,严重的还会造成很大的性能问题。

  • 这时候可能有人会说用SpringAOP定义切面去处理所有Controller,然后有异常的时候统一拦截起来。

  • 但是这种呆瓜操作,Spring MVC早就给我们想好了,无需我们自己取定义。

1.2 最佳解决方案

Spring MVC给我们提供了一个@RestControllerAdvice来增强所有@RestController,然后使用@ExceptionHandler注解,就可以拦截到对应的异常。

二、使用Spring MVC的统一异常处理机制

2.1 和前端约定好的通用返回结果类

这一步是非必须,但是项目中一般都会约定好一个通用的消息返回格式。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
 * 通用返回结果类
 * @param <T>    通用泛型(传了这个泛型的话, 数据data的类型就和这个泛型一致)
 *               这个T可以理解为type类型。
 *               使用其它字母也行,但是最好能保持一定的语义化,让大家更容易理解。
 */
@Data                          //lombok注解 提供get/set、toString、equal等实体类都有的方法
@NoArgsConstructor             //lombok注解 提供无参构造
@AllArgsConstructor            //lombok注解 提供全参构造
public class ReturnMessage<T> implements Serializable {

    private Integer code;    //返回状态码
    private String msg;      //错误信息
    private T data;          //数据
    
    /**
     * 创建一个要返回的消息对象
     * @param code  状态码
     * @param msg   错误信息
     * @param data  数据
     * @return
     * @param <T>   声明这个静态方法是一个泛型方法
     */
    public static <T> ReturnMessage<T> create(Integer code, String msg, T data) {
        ReturnMessage<T> r = new ReturnMessage<T>(code, msg, data);
        return r;
    }

}

2.2 定义响应码枚举类

项目中通常会约定好返回给前端的状态码,为了代码得优雅和可维护性,可以使用一个枚举类来管理

import lombok.Getter;

@Getter	 //lombok注解 提供各属性的get方法
public enum AppCode {

    SUCCESS(200, "成功"),
    SERVICE_ERROR(500, "服务器异常"),
	PASSWORD_ERROR(5001, "密码错误"),
    CHECK_ERROR(5002, "验证码无效");
        
    private int code;
    private String msg;

    AppCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

2.3 自定义业务异常类

import com.tanhua.model.vo.ErrorResult;
import lombok.Data;

@Data
public class BusinessException extends RuntimeException{

    private AppCode appCode;		//异常状态码对象

    public BusinessException(AppCode appCode) {
        super(appCode.getMsg());
        this.appCode = appCode;
    }
}

2.4 自定义统一异常处理类

import com.tanhua.model.vo.ErrorResult;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 *  自定义统一异常处理类
 *	步骤:
 *  	1、通过注解,声明异常处理类
 *  	2、编写方法,在方法内部处理异常,构造响应数据
 *  	3、方法上编写注解@ExceptionHandler,指定此方法可以处理的异常类型
 *
 *	使用Spring推荐的ResponseEntity作为返回值时:
 *		1. 可以手动修改返回的状态码和响应体
 *		2. 此类不需要加@ResponseBody
 *
 *	如果是自定义的返回结果类作为返回值时:
 *		1.比如直接使用上面的ReturnMessage作为返回值, 则需要在此类或者方法上添加@ResponseBody
 *	    2.此时可以使用@RestControllerAdvice注解, 它等同于ControllerAdvice + @ResponseBody
 */

@ControllerAdvice
public class ExceptionAdvice {

    //处理业务异常
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity handlerBusinessException(BusinessException be) {
        be.printStackTrace();
        AppCode appCode = be.getAppCode();
        ReturnMessage returnMessage = new ReturnMessage(
                                                        appCode.getCode(),
                                                        appCode.getMsg(),
                                                        null);
		return ResponseEntity.status(appCode.getCode())
            				 .body(returnMessage);
    }

    //处理不可预知的异常 (像什么空指针、数组下标越界、类型转换等非业务异常)
    @ExceptionHandler(Exception.class)
    public ResponseEntity handlerException(Exception e) {
        e.printStackTrace();      
        ReturnMessage returnMessage = new ReturnMessage(
                                                        AppCode.SERVICE_ERROR.getCode(),
                                                        e.getMessage(),
                                                        null);
        /**
         *   拓展:
         *   HttpStatus 是Spring自带的状态码信息类
         *   源码中的定义 :
         *           INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
         *   可以看到 INTERNAL_SERVER_ERROR代表的就是500状态码,服务端异常
         *   这种写法语义性更强,且不用自己再去定义
         *    
         * 	 但是我这里并没有使用,而是选择了自定义状态码
        */
        return ResponseEntity.status(AppCode.SERVICE_ERROR.getCode())
            				 .body(returnMessage);
    }

}

2.5 遇到业务异常时的处理示例:

public Boolean loginVerification(String phone, String password) {
	User user = userService.findByPhone(phone);
    if(user.getPassword != password) {
		//密码不相等,抛出自定义业务异常
        throw new BusinessException(ErrorResult.passswordError());
        return false;
    }
    return true;
}

2.6 统一异常异常执行原理

  1. 定义好了统一异常处理,再遇到业务异常,可以直接throw new BusinessException()的方式抛出异常

  2. 此时我们定义的ExceptionAdvice类就会根据handlerBusinessException方法去构建错误信息返回给前端。

  3. 而其它项目中的空指针、数组下标越界、类型转换等非业务异常,交由handlerException处理。

    这样就实现了项目中的统一异常处理。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值