浅谈springboot异常处理机制

6 篇文章 0 订阅
5 篇文章 0 订阅

项目中对于全局的异常处理是非常有必要的,对用户来说体验可以更加友好,对系统来说可以追溯异常信息,找到异常出处。
springboot中又是如何处理异常的呢?

spring 中有一个处理异常的接口

public interface HandlerExceptionResolver {
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

实现该接口,注册到spring容器中,当controller中产生异常的时候会调用该接口来处理,注意,当返回值指定视图时会自动跳转至指定的视图中去,如果返回null,会继续调用下一个异常处理器去执行。
springboot中可以通过以下方式去注册进容器

    @Bean
    public ApplicationHandlerExceptionResolver handlerExceptionResolver(){
        return new ApplicationHandlerExceptionResolver();
    }

或者

@EnableWebMvc
@EnableAsync
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        super.configureHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(new ApplicationHandlerExceptionResolver());
    }
}

spring 中还可以通过@ControllerAdvice以及@ExceptionHandler(ApplicationException.class)注解对指定的异常做全局处理

@Controller
@ControllerAdvice
@RequestMapping("/error/")
public class GlobalExceptionController{

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionController.class);

    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("控制器增强处理异常");
        return Result.serverError();
    }
}

如果同时以上面的方式配置这2种,你会发现第二种处理方式不起作用,这是为什么?查看源码得知,在
WebMvcConfigurationSupport类中有这么一个方法

    @Bean
    public HandlerExceptionResolver handlerExceptionResolver() {
        List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
        configureHandlerExceptionResolvers(exceptionResolvers);
        if (exceptionResolvers.isEmpty()) {
            addDefaultHandlerExceptionResolvers(exceptionResolvers);
        }
        extendHandlerExceptionResolvers(exceptionResolvers);
        HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
        composite.setOrder(0);
        composite.setExceptionResolvers(exceptionResolvers);
        return composite;
    }

注意上面方法里面的 configureHandlerExceptionResolvers(exceptionResolvers); 这个方法的实现正好是我们所覆盖的,如果我们添加了异常处理器,那么就不会再添加默认的异常处理器了,而第二种异常处理的方式恰恰是通过默认的异常处理器来完成的,那么如何才能让二者都生效呢
继续看这一行 extendHandlerExceptionResolvers(exceptionResolvers); 这个方法会把我们注册的异常处理器和默认的处理器合并,所以我们需要重写这个方法

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        super.extendHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(new ApplicationHandlerExceptionResolver());
    }

这样就能将默认的异常解析器和自定义的解析器都注册进去了,但是测试的时候你会发现,实现HandlerExceptionResolver 接口的异常处理器不会生效,这是为何?原因在于,异常注册器是按顺序执行的,当前一个返回的ModelAndView对象不为null时就会中断后续的异常处理器执行,而

    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("控制器增强处理异常");
        return Result.serverError();
    }

执行完之后会返回一个ModelAndView对象,所以不会再执行我们自己注册的异常解析器了,要想生效,我们只需在注册时调整注册顺序即可

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        super.extendHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(0, new ApplicationHandlerExceptionResolver());
    }

将我们自定义的异常解析器注册到首位即可。
spring 中异常处理的方式大致是这样,但这2种方式只能处理Controller层之内的异常,对于渲染层及其他的异常是无能为力的,如果我们要做全局的异常处理就需要结合容器来做处理了。

以tomcat为例,容器在处理请求时,遇到异常情况,首先会去找错误页面,如果没有配置错误页面,会HttpStatus默认转发到/error路径,所以我们可以写一个controller类来处理/error 请求,而springboot中默认有一个处理/error 请求的controller类

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties,
                Collections.<ErrorViewResolver>emptyList());
    }
    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    @Override
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
    protected boolean isIncludeStackTrace(HttpServletRequest request,
            MediaType produces) {
        IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

}

我们可以写个controller继承BasicErrorController 达到自定义处理的目的

@ControllerAdvice
@RequestMapping("/error/")
public class GlobalExceptionController extends BasicErrorController {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionController.class);

    private ErrorAttributes errorAttributes;

    public GlobalExceptionController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
        this.errorAttributes = errorAttributes;
    }

    @RequestMapping(value = SystemConstant.ERROR_NO_AUTH_PATH)
    public Result noAuth() {
        return Result.noAuth();
    }

    @RequestMapping(value = SystemConstant.URL_NO_EXISTS)
    public Result noExists() {
        return Result.urlNoExists();
    }

    @RequestMapping(value = SystemConstant.SERVER_ERROR)
    public Result serverError() {
        return Result.serverError();
    }

    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("控制器增强处理异常");
        return Result.serverError();
    }
}

我们还可以在tomcat中配置错误码及转发路径,以便集中处理异常

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
                container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400"));
                container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/400"));
                container.addErrorPages(new ErrorPage(HttpStatus.BAD_GATEWAY, "/error/500"));
                container.addErrorPages(new ErrorPage(Throwable.class, "/error/500"));
                container.setDisplayName("ABC");
                container.setPort(80);
            }
        };
    }

以上便是springboot中的异常处理方式

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值