项目中对于全局的异常处理是非常有必要的,对用户来说体验可以更加友好,对系统来说可以追溯异常信息,找到异常出处。
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中的异常处理方式