全局异常处理

为了达到系统的各个模块中都能够共用同一个异常处理逻辑,避免代码重复和错误。在Spring框架中,可以通过全局异常处理来捕获应用程序中抛出的异常,并根据需要进行处理。

@ControllerAdvice

@ControllerAdvice是Spring MVC框架中的一个注解,用于定义一个全局的异常处理器和绑定响应数据的方法。

当Spring MVC中的Controller抛出异常时,它会查找@ControllerAdvice注解标注的类,然后调用其中匹配异常类型的方法来处理异常。这些方法可以返回一个ModelAndView对象、一个ResponseEntity对象或者其它任意类型的对象。此外,这些方法也可以通过@ExceptionHandler注解将响应数据绑定到请求中,使其可在视图中渲染。

@ControllerAdvice注解可以用于定义全局的异常处理器,以便在系统的各个模块中都能够共用同一个异常处理逻辑,避免代码重复和错误。例如,在处理Web应用程序的RESTful API时,可以使用@ControllerAdvice注解定义一个异常处理器,以便在出现异常时返回一个统一的错误响应格式。

@ControllerAdvice注解也可以用于定义全局的数据绑定方法,以便在处理请求时,自动将一些公共的响应数据绑定到请求中,避免了在Controller中的方法中的重复绑定。

@ControllerAdvice注解通常用于Web应用程序中,可以与@ExceptionHandler、@InitBinder和@ModelAttribute注解一起使用。

定义全局异常处理类

定义一个全局异常处理类,通过@ControllerAdvice注解,其中定义了匹配异常类型的方法来处理异常,用注册@ExceptionHandler({Throwable.class})注解方法,Throwable.class为匹配的异常类型。

/**
 * 全局异常.
 *
 * @author <a rel="nofollow" href="mailto:felix@gmail.com">lht</a>
 * @date 2023/4/17 16:27
 * @since 1.0.0
 **/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private MessageSource messageSource;

    /**
     * Throwable异常处理.
     *
     * @param e       e
     * @param request request
     * @return ResponseEntity
     */
    @ExceptionHandler({Throwable.class})
    public ResponseEntity<R> handleGlobalExceptionResponse(final Throwable e, final HttpServletRequest request) {
        log.error("handleGlobalException: ", e);
        return ExceptionHandlerUtil.handleGlobalException(this.messageSource);
    }

    /**
     * BusinessException异常处理.
     *
     * @param businessException businessException
     * @param request           request
     * @return ResponseEntity
     */
    @ExceptionHandler({BusinessException.class})
    public ResponseEntity<R> handleServiceExceptionResponse(final BusinessException businessException, final HttpServletRequest request) {
        return ExceptionHandlerUtil.handleBusinessException(this.messageSource, businessException);
    }

    /**
     * MethodArgumentNotValidException异常处理.
     *
     * @param e       e
     * @param request request
     * @return result
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public ResponseEntity<R> handleMethodArgumentNotValidException(final MethodArgumentNotValidException e, final HttpServletRequest request) {
        String key = ((ObjectError) e.getBindingResult().getAllErrors().get(0)).getDefaultMessage();
        return ExceptionHandlerUtil.handleBusinessException(this.messageSource, new BusinessException(key));
    }

}

ExceptionHandlerUtil工具类,可以将国际化配置中的内容通过MessageSource获取后解析后返回给前端,messageSource的key为自定义异常中的messageKey,参考后面的代码BusinessException。

/**
 * 全局异常工具类.
 *
 * @author <a rel="nofollow" href="mailto:felix@gmail.com">lht</a>
 * @date 2023/4/17 16:27
 * @since 1.0.0
 **/
@Slf4j
public class ExceptionHandlerUtil {

    private static final String RES_DEFAULT_ERROR_MSG = "global.server.internal.error";

    public ExceptionHandlerUtil() {
    }

    /**
     * 全局异常.
     *
     * @param msgSource 国际化配置
     * @return responseEntity
     */
    public static ResponseEntity<R> handleGlobalException(final MessageSource msgSource) {
        String msgValue = getGlobalMessage(RES_DEFAULT_ERROR_MSG);

        try {
            msgValue = msgSource.getMessage(RES_DEFAULT_ERROR_MSG, (Object[]) null, (Locale) null);
        } catch (NoSuchMessageException var4) {
            log.warn("解析messageKey:{} 全局异常错误信息:{}", RES_DEFAULT_ERROR_MSG, var4.getMessage());
        }

        return responseEntity(msgValue);
    }

    /**
     * businessException异常处理.
     *
     * @param msgSource         国际化配置
     * @param businessException 异常
     * @return responseEntity
     */
    public static ResponseEntity<R> handleBusinessException(final MessageSource msgSource, final BusinessException businessException) {
        String msgValue = getDefaultMessage(businessException.getMessageKey());

        try {
            msgValue = msgSource.getMessage(businessException.getMessageKey(), businessException.getValues(), (Locale) null);
        } catch (NoSuchMessageException var) {
            log.warn("BusinessException-解析messageKey:{} BusinessException-异常错误信息:{}", businessException.getMessageKey(), var.getMessage());
        }
        return responseEntity(msgValue);
    }

    /**
     * responseEntity.
     *
     * @param msgValue 配置的值
     * @return responseEntity
     */
    private static ResponseEntity<R> responseEntity(final String msgValue) {
        String[] values = msgValue.split(";");
        if (values.length < 3) {
            values = new String[]{String.valueOf(400), "system.unknown", "400"};
        }
        ResponseEntity<R> resultResponseEntity = new ResponseEntity(R.error(0, values[2] == null ? "400" : values[2], values[1] == null ? RES_DEFAULT_ERROR_MSG : values[1]), HttpStatus.
                //默认取400
                        valueOf(Integer.parseInt(values[0])));
        return resultResponseEntity;
    }

    /**
     * businessException默认的message.
     *
     * @param messageKey messageKey
     * @return messageKey
     */
    private static String getDefaultMessage(final String messageKey) {
        return "400;" + messageKey + ";" + "400";
    }

    /**
     * 全局异常message.
     *
     * @param messageKey
     * @return messageKey
     */
    private static String getGlobalMessage(final String messageKey) {
        return "500;" + messageKey + ";" + "500";
    }
}

也可以自定义异常去匹配处理,如上面的@ExceptionHandler({BusinessException.class})的方法,BusinessException就是自定义异常处理类:


/**
 * @author <a rel="nofollow" href="mailto:felix@gmail.com">lht</a>
 * @date 2023/4/17 14:39
 * @since 1.0.0
 **/
public class BusinessException extends CommonException {

    public BusinessException(final String messageKey, final Object... values) {
        super(messageKey, values);
    }

    public BusinessException(final String messageKey) {
        super(messageKey);
    }

    public BusinessException(final Throwable cause, final String messageKey, final Object... values) {
        super(cause, messageKey, values);
    }

    public BusinessException(final Throwable cause, final String messageKey) {
        super(cause, messageKey);
    }

}

当项目抛出BusinessException,会匹配到@ExceptionHandler({BusinessException.class})注解的方法,BusinessException的messageKey可以自定义,并且可以配置到国际化配置中。

示例

定义一个接口,抛出异常BusinessException。

this.test.error是自定义配置在配置文件中的key,配置中的ASCII码含义是这是一个错误码测试,value后面的400001为返回的code码。

请求后返回