2 入门级搭建Java后端开发框架-2-统一异常处理

152 阅读3分钟

为什么需要统一异常处理: 在后端发生异常或者请求出错时,如果前端展示如下:

There was an unexpected error (type=Not Found, status=404).
No message available

对于用户来说非常不友好,用户看了还是不明白具体的错误信息,往往后端就需要在代码块里加上try / catch,捕获该异常以一种更友好的方式告诉用户发生了何种异常,比如:"该页面不存在,请检查访问路径是否正确!"。当业务逻辑很多,代码很多,如果在每个业务逻辑处都上面try / catch,就显得代码很乱很臃肿。

全局异常处理: 通过Spring的AOP特性使用@ControllerAdvice、@ExceptionHandler定义全局的运行时异常处理。

1、在common创建exception子目录,通用异常处理设计的类均放在该目录下:

2、新建通用业务异常接口BaseException:

package com.cxzx.cnctest.common.exception;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 9:24
 */
public interface BaseException {
    String getErrorCode();
    String getErrorMsg();
}

3、新建异常code的枚举类BizCodeEnum:

package com.cxzx.cnctest.common.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 11:11
 */
@Getter
@AllArgsConstructor
public enum BizCodeEnum implements BaseException{
    // ----------- 通用异常状态码 -----------
    SYSTEM_ERROR("10000", "出错啦,后台小哥正在努力修复中..."),

    ;

    // 异常码
    private String errorCode;
    // 错误信息
    private String errorMsg;
}

4、新建通用业务异常处理类:BizException:

package com.cxzx.cnctest.common.exception;

import lombok.Getter;
import lombok.Setter;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 11:22
 */
@Getter
@Setter
public class BizException extends RuntimeException{
    // 异常码
    private String errorCode;
    // 错误信息
    private String errorMsg;

    public BizException(BaseException baseException) {
        this.errorCode = baseException.getErrorCode();
        this.errorMsg = baseException.getErrorMsg();
    }
    public BizException(String errorMsg){
        this.errorMsg = errorMsg;
    }
    public BizException(String errorCode, String errorMsg){
        this.errorMsg = errorMsg;
    }
}

BaseResponse新增fail方法(入参为BaseException和BizException):

public static <T> BaseResponse<T> fail(BaseException baseException) {
    BaseResponse<T> response = new BaseResponse<>();
    response.setSuccess(false);
    response.setErrorCode(baseException.getErrorCode());
    response.setErrorMsg(baseException.getErrorMsg());
    return response;
}

public static <T> BaseResponse<T> fail(BizException bizException) {
        BaseResponse<T> response = new BaseResponse<>();
        response.setSuccess(false);
        response.setErrorCode(bizException.getErrorCode());
        response.setErrorMsg(bizException.getErrorMsg());
        return response;
    }

5、定义全局异常处理类 GlobalExceptionHandler:

common层的pom.xml引入web的依赖(@ControllerAdvice注解的类在该依赖里)

<!-- ........ 省略 -->
<dependencies>
        <!-- ........ 省略 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
<!-- ........ 省略 -->

该类的代码如下:

package com.cxzx.cnctest.common.exception;

import com.cxzx.cnctest.common.utils.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 9:15
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    //捕获通用异常
    @ExceptionHandler({ Exception.class })
    @ResponseBody
    public BaseResponse<Object> handleCommonException(HttpServletRequest request, Exception e) {
        log.error("{} request error, ", request.getRequestURI(), e);
        return BaseResponse.fail(BizCodeEnum.SYSTEM_ERROR);
    }

    // 捕获业务异常
    @ExceptionHandler({BizException.class})
    @ResponseBody
    public BaseResponse<Object> handleBizException(HttpServletRequest request, BizException e) {
        log.error("{} request fail, errorCode: {}, errorMsg: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
        return BaseResponse.fail(e);
    }
}

最终common层异常类定义如下:

6、测试效果:

starter层新建测试controller:

package com.cxzx.cnctest.start.test;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 13:47
 */
@RestController
@RequestMapping("/cnc")
public class TestController {

    @GetMapping("/test")
    public String test(@RequestParam(name = "test") String name){
        return name;
    }
}
  • 比如浏览器访问该path,制造参数名称错误:

http://localhost:8080/cnc/test?temp=test

后端controller层捕获到异常后按照异常处理返回的格式返回数据给前端:

前端就可以提取errorMsg展示给用户看。

  • 再比如主动抛出业务异常

BizCodeEnum 定义业务异常code:

PRODUCT_NOT_EXISTS("10001","该产品不存在"),

service层新建TestService类,代码如下:

package com.cxzx.cnctest.service;

import com.cxzx.cnctest.common.exception.BizCodeEnum;
import com.cxzx.cnctest.common.exception.BizException;
import org.springframework.stereotype.Service;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 17:18
 */
@Service
public class TestService {
    public String test(){
        throw new BizException(BizCodeEnum.PRODUCT_NOT_EXISTS);
    }
}

TestController 类中调用该service的方法

package com.cxzx.cnctest.start.test;

import com.cxzx.cnctest.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 某角落的IT打杂工
 * @version 1.0
 * @description: TODO
 * @date 2024/5/28 13:47
 */
@RestController
@RequestMapping("/cnc")
public class TestController {

    @Autowired
    TestService testService;

    @GetMapping("/test")
    public String test(@RequestParam(name = "test") String name){
        return testService.test();
    }
}

浏览器中访问:http://localhost:8080/cnc/test?test=test

抛出业务异常: