目录
方法一:使用@ExceptionHandler以及@ControllerAdvice
方法二:使用HandlerExceptionResolver处理全局异常【不推荐】
方法三:使用Springboot提供的全局异常处理方法
参考springmvc与springboot的全局异常处理方法【附源码】_mybabe0312_51CTO博客
在默认情况下,Springmvc会使用ExceptionHandlerExceptionResolver来作为异常解析处理器,其作用就是支持使用@ExceptionHandler以及@ControllerAdvice的方式来处理全局异常。
一般来说其使用方式有两种,一种是在Controller中创建具有@ExceptionHandler注解的方法来处理异常,这种方式只会对自身Controller的异常有效; 另一种方式是配合@ControllerAdvice注解来创建异常解析处理器,通过这种方式定义的异常处理方法将是全局的,对所有Controller抛出的异常都有效;
主要执行过程
ExceptionHandlerExceptionResolver初始化时的主要动作:
从IOC中查找所有带有 @ControllerAdvice注解的对象 得到@ControllerAdvice注解的对象中所有标注@ExceptionHandler注解的方法 根据@ExceptionHandler注解标注的异常类型作为key,其Method作为value进行缓存【作为全局异常处理器】 当捕获到抛出的异常时,ExceptionHandlerExceptionResolver将进行以下几个主要步骤的处理
将HandlerMethod所在类型封装到ExceptionHandlerMethodResolver中; 通过ExceptionHandlerMethodResolver来解析和查找可以处理所捕获异常的方法(与@ExceptionHandler注解配置中异常匹配的方法); 如果没有找到则从缓存中匹配全局异常处理的ExceptionHandlerMethodResolver; 如果没有找到则返回null,否则封装成ServletInvocableHandlerMethod返回; 执行ServletInvocableHandlerMethod的invokeAndHandle方法来处理异常,并将返回结果使用returnValueHandlers里面保存的返回结果解析器进行处理封装成ModelAndView返回;
示例
@RestControllerAdvice public class GlobalControllerExceptionHandler {
/** * 参数校验异常 * */ @ExceptionHandler(MethodArgumentNotValidException.class) public BaseResponse errorHandler(MethodArgumentNotValidException exception){ return null ; } /** * 上传的异常 * */ @ExceptionHandler(MultipartException.class) public BaseResponse errorHandler(MultipartException exception){ return null ; } /** * 没有找到Handler异常 * */ @ExceptionHandler(NoHandlerFoundException.class) public BaseResponse errorHandler(NoHandlerFoundException exception){ return null ; } /** * 处理其它没能捕获到的异常 * */ @ExceptionHandler(Throwable.class) public BaseResponse errorHandler(Throwable exception){ return null ; }
这种方式是我们实现HandlerExceptionResolver接口来自定义异常解析处理器,同时将其放入IOC容器即可。之所以不推荐使用这种方式,是因为这种方式会导致springmvc默认的三种异常解析器失效,也会导致@ExceptionHandler的方式就不可用了(DispatcherServlet在初始化时如果发现IOC中有HandlerExceptionResolver类型的Bean则不使用默认的)。
@Component public class GlobalExceptionHandlerExceptionResolver implements HandlerExceptionResolver { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerExceptionResolver.class) ; /** * 如果返回的ModelAndView为null或这个视图处理出现了异常,则异常会被继续跑出去 * */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null); //判断是否为ajax请求 ModelAndView mv = null ; if(isAjaxRequest(request)){ mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("no") ; }else{ mv = new ModelAndView("error"); mv.addObject("ex",ex) ; } log.error("handler throws exception",ex); return mv; } public static boolean isAjaxRequest(HttpServletRequest request) { String accept = request.getHeader("accept"); if (accept != null && accept.indexOf("application/json") != -1) { return true; } String xRequestedWith = request.getHeader("X-Requested-With"); if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) { return true; } String ajax = request.getHeader("__ajax"); if (StringUtils.isNotBlank(ajax)) { return true; } return false; } }
springmvc默认的三个异常处理解析器: 1,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; 2,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; 3,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
上面两种方法仅能处理SpringMVC框架抛出的异常,如果我们还集成了SpringSecurity等其它框架,或者有自己创建的不被Spring框架管理的Servlet,并且使用了Springboot框架时,那么我们就可以使用这种方式来处理全局异常了(当然一般会与@ControllerAdvice方式配合)。
这种方式的原理大致如下: 在ServletWebServerFactoryAutoConfiguration中通过@Import的方式将ErrorPageRegistrarBeanPostProcessor添加到容器中; ErrorPageRegistrarBeanPostProcessor 对象便可在Bean创建后被执行,从而可对Bean实例进行处理
ErrorPageRegistrarBeanPostProcessor执行时,会从beanFactory中取到所有ErrorPageRegistrar类型的对象,同时对ErrorPageRegistry类型的Bean作为参数调用ErrorPageRegistrar类型对象的registerErrorPages方法来对ErrorPageRegistry进行装配;
在springboot启动过程中会通过ErrorPageFilterConfiguration来为Servlet容器添加一个ErrorPageFilter(参考SpringBootServletInitializer)。该ErrorPageFilter中可以根据状态码或捕获到的异常的类型来调整到指定的错误路径下(如果没有则使用全局的路径),如果全局的路径都没设置,则会将异常继续跑出去;
同时,ErrorPageFilter是ErrorPageRegistry的子类,所以会被beanFactory中的ErrorPageRegistrar类型实例所装配
而在ErrorMvcAutoConfiguration中便会创建ErrorPageCustomizer实例来完成ErrorPageFilter的默认配置(例如:全局的错误路径,其默认为“/error”,可通过error.path进行配置)
我们可以自定义ErrorPageRegistrar来对ErrorPageFilter进行配置
@Configuration(proxyBeanMethods = false) public class GlobalBootErrorPageConfiguration {
public static class CustomErrorPageRegistrar implements ErrorPageRegistrar, Ordered { @Override public void registerErrorPages(ErrorPageRegistry registry) { ErrorPage globalErrorPage = new ErrorPage("/error") ; ErrorPage statusErrorPage = new ErrorPage(HttpStatus.BAD_GATEWAY,"/error") ; ErrorPage exceptionErrorPage = new ErrorPage(Exception.class,"/error") ; registry.addErrorPages(globalErrorPage); registry.addErrorPages(statusErrorPage); registry.addErrorPages(exceptionErrorPage); } /** * 默认的ErrorPageCustomizer配置其order为0,我们可以通过返回比起大的值来覆盖全局的错误路径 * */ @Override public int getOrder() { return 2; } } @Bean public ErrorPageRegistrar customErrorPageRegistrar(){ return new CustomErrorPageRegistrar() ; } }
我们还可以覆盖springboot中默认的错误页面
@Configuration(proxyBeanMethods = false) public class GlobalBootErrorPageConfiguration { public static class CustomErrorPageRegistrar implements ErrorPageRegistrar, Ordered { @Override public void registerErrorPages(ErrorPageRegistry registry) { ErrorPage globalErrorPage = new ErrorPage("/error") ; ErrorPage statusErrorPage = new ErrorPage(HttpStatus.BAD_GATEWAY,"/error") ; ErrorPage exceptionErrorPage = new ErrorPage(Exception.class,"/error") ; registry.addErrorPages(globalErrorPage); registry.addErrorPages(statusErrorPage); registry.addErrorPages(exceptionErrorPage); } /** * 默认的ErrorPageCustomizer配置其order为0,我们可以通过返回比起大的值来覆盖全局的错误路径 * */ @Override public int getOrder() { return 2; } } @Bean public ErrorPageRegistrar customErrorPageRegistrar(){ return new CustomErrorPageRegistrar() ; } @Configuration(proxyBeanMethods = false) public static class ErrorViewConfiguration { @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; } private final ErrorView defaultErrorView = new ErrorView(); @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } } /** * 可参考:ErrorMvcAutoConfiguration.StaticView * */ public static class ErrorView implements View { @Override public void render(Mapmodel, HttpServletRequest request, HttpServletResponse response) throws Exception { } @Override public String getContentType() { return "text/html"; } }