SpringBoot | 常见几种异常配置方案

24 篇文章 14 订阅
12 篇文章 5 订阅

微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
在这里插入图片描述


在上一篇博客中,分析了spring 和 springboot异常的源码执行流程,《SpringBoot异常机制源码分析》,解析流程梳理之后,接下来主要对我们项目中的异常配置,做一个整理。

在使用spring、springBoot的项目中,常见的配置大概有以下三种方案:

1)使用@ControllerAdvice注解,自定义 exceptionHandler,继承 ResponseEntityExceptionHandler,可以配置多个exceptionHandler。
在上篇的异常解析流程中,我们已经提到了,异常解析会先匹配 exceptionHandler中所标识的异常:

@ExceptionHandler(NotFoundException.class)

完整配置如下:

/**
 * Created by zhangshukang on 2017/08/31.
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    @Value("${spring.application.name:}")
    private String systemName;

    @ExceptionHandler(BadRequestException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleBadRequestException(HttpServletRequest request, HttpServletResponse response, BadRequestException ex) {
        log.error(ex.getMessage(), ex);
        return new ResponseEntity<>(ex.getMessageList(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleBusinessException(HttpServletRequest request, HttpServletResponse response, BusinessException ex) {
        log.error(ErrorLogInfo.build(ex.getSrcClass(), systemName, ex.getErrorCode(), ex.getMessage()).toJson());
        Throwable orgEx = ex.getOriginalException();
        if (orgEx != null)
            log.error(orgEx.getMessage(), orgEx);
        else
            log.error(ex.getMessage(), ex);

        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessageList());
    }

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleUnauthorizedException(HttpServletRequest request, HttpServletResponse response, UnauthorizedException ex) {
        log.error(ErrorLogInfo.build(ex.getSrcClass(), systemName, ex.getErrorCode(), ex.getMessage(), ex.getLoginId()).toJson());
        log.error(ex.getMessage(), ex);
        return new ResponseEntity<>(ex.getMessageList(), HttpStatus.UNAUTHORIZED);
    }

    @ExceptionHandler(NotFoundException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleNotFoundException(HttpServletRequest request, HttpServletResponse response, NotFoundException ex) {
        log.warn(ex.getMessage(), ex);
        if (ex.getMessageList() == null) {
            NotFoundException ne = new NotFoundException(BaseException.ERR_9996, this);
            return new ResponseEntity<>(ne.getMessageList(), HttpStatus.NOT_FOUND);
        } else
            return new ResponseEntity<>(ex.getMessageList(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(ForbiddenException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleForbiddenException(HttpServletRequest request, HttpServletResponse response, ForbiddenException ex) {
        log.error(ErrorLogInfo.build(ex.getSrcClass(), systemName, ex.getErrorCode(), ex.getMessage()).toJson());
        log.error(ex.getMessage(), ex);
        return new ResponseEntity<>(ex.getMessageList(), HttpStatus.FORBIDDEN);
    }

    @ExceptionHandler(MovedPermanentlyException.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleMovedPermanentlyException(HttpServletRequest request, HttpServletResponse response, MovedPermanentlyException ex) {
        log.warn(ErrorLogInfo.build(ex.getSrcClass(), systemName, ex.getErrorCode(), ex.getMessage()).toJson());
        return new ResponseEntity<>(ex.getMessageList(), HttpStatus.MOVED_PERMANENTLY);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<MessageVo> handleException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        log.error(ErrorLogInfo.build(ex, systemName, BaseException.ERR_9999, ex.getMessage()).toJson());
        log.error(ex.getMessage(), ex);
        MessageVo messages = new MessageVo();
        messages.addMessageObj(BaseException.ERR_9999, ex.getMessage(), "");
        return new ResponseEntity<>(messages, HttpStatus.INTERNAL_SERVER_ERROR);
    }

}
2)在第一种情况没有找到exceptionHandler 或没有解析到对应的异常 的情况下,或根据全局错误路径,访问 AbstractErrorController。也可以使用默认的错误路径访问类:BasicErrorController 配置统一错误路径访问类:
@Controller
@Slf4j
public class GlobalExceptionController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public GlobalExceptionController(){
        this(new DefaultErrorAttributes());
    }
    public GlobalExceptionController(ErrorAttributes errorAttributes) {
        this(errorAttributes, new ErrorProperties());
    }
    public GlobalExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        super(errorAttributes);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    private static final String PATH = "/error";

    @Override
    public String getErrorPath() {
        return PATH;
    }

    @RequestMapping("${server.error.path:${error.path:/error}}")
    @ResponseBody
    public ResponseEntity error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        String errMsg = getErrorMessage(request) != null ? getErrorMessage(request) : (String)body.get("message");
        log.error("url:{},error:{},message:{}", body.get("path"), body.get("error"), errMsg);
        if(status == HttpStatus.NOT_FOUND) {
            NotFoundException ne = new NotFoundException(BaseException.ERR_9996, this);
            return new ResponseEntity(ne.getMessageList(), status);
        } else {
            MessageVo msg = new MessageVo();
            msg.addMessageObj(BaseException.ERR_9999, errMsg, null);
            return new ResponseEntity(msg, status);
        }
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    private String getErrorMessage(HttpServletRequest request) {
        final Throwable exc = (Throwable) request.getAttribute("javax.servlet.error.exception");
        return exc != null ? exc.getMessage() : null;
    }
}

3)自定义的统一异常处理类:HandlerExceptionResolver 子类,是最后的异常处理策略。

 public class MyHandlerException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object object,
                                         Exception e) {
        ModelAndView mav = new ModelAndView();
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        Map<String, Object> attributes = new HashMap<String, Object>();
        attributes.put("code", "500");
        attributes.put("message", "系统繁忙");
        view.setAttributesMap(attributes);
        view.setContentType("application/json;charset=UTF-8");
        mav.setView(view);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return mav;

    }
}

只有当 exceptionHandler 不存在 和 未定义带有 responseStatus的异常的情况下,
才会采用此异常策略。
例如抛出如下这种异常,不含responseStatus的情况下,才会进入自定义的统一异常解析类

//@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NotFoundException extends BaseException {

    private static final long serialVersionUID = -8687066286979480116L;

    public NotFoundException() {
        super();
    }
    public NotFoundException(MessageVo messages, Object srcClass) {
    	super();
    	setSrcClass(srcClass);
    	setMessageVo(messages);
    }

}


总结:尽量采用第一种和第二种结合的配置,对异常处理够灵活。以前公司的项目还是spring项目的时候,采用的是第三种,这样只能控制全局异常,而一些业务异常,解析后不能有一些自己的逻辑,例如,构造返回状态实体responseEntity的逻辑,不够灵活。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值