Spring Boot全局异常处理
使用注解方式处理全局异常
使用 @ControllerAdvice (@RestControllerAdvice) 配合 @ExceptionHandler
适用于返回数据的请求(一般是RESTful接口规范下的JSON报文)
package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.example.exception.ControllerController.MyException1;@RestControllerAdvice
public class RestControllerExceptionAdvice {private static transient Logger logger = LoggerFactory.getLogger(RestControllerExceptionAdvice.class);@ExceptionHandler(MyException1.class)public String MyException1(MyException1 e) {logger.info("RestControllerExceptionAdvice MyException1 异常->{}", e.getMessage());return "error";}
}
适用于返回视图的请求,方法返回值如果不是ModelAndView而是字符串时,字符串会被解析成视图名
package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import com.example.exception.ControllerController.MyException1;
import com.example.exception.ControllerController.MyException3;@ControllerAdvice
public class ControllerExceptionAdvice {private static transient Logger logger = LoggerFactory.getLogger(ControllerExceptionAdvice.class);@ExceptionHandler(MyException1.class)public String uploadException(MyException1 e) {logger.info("ControllerExceptionAdvice MyException1 异常->{}", e.getMessage());return "this is a viewName1";}@ExceptionHandler(MyException3.class)public ModelAndView uploadException(MyException3 e) {logger.info("ControllerExceptionAdvice MyException3 异常->{}", e.getMessage());return new ModelAndView("this is a viewName3");}
}
也可以@ControllerAdvice配合使用@ResponseBody完成和@RestControllerAdvice一样的效果(不建议这样使用,个人感觉会使代码很混乱)
package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.example.exception.ControllerController.MyException1;
import com.example.exception.ControllerController.MyException2;
import com.example.exception.ControllerController.MyException3;@ControllerAdvice
public class ControllerExceptionAdvice {private static transient Logger logger = LoggerFactory.getLogger(ControllerExceptionAdvice.class);@ExceptionHandler(MyException2.class)@ResponseBodypublic String uploadException(MyException2 e) {logger.info("ControllerExceptionAdvice MyException2 异常->{}", e.getMessage());return "this is a rsp";}
}
控制器私有的异常处理器,优先级高于全局异常处理器
package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class RestControllerController {private static transient Logger logger = LoggerFactory.getLogger(RestControllerController.class);@GetMapping("/test")@ResponseBodypublic String testExceptionResolver() {throw new RuntimeException("这是测试控制器异常");}// 只针对当前控制器,优先级高于全局异常处理器@ResponseBody@ExceptionHandler(RuntimeException.class)public String uploadException(RuntimeException e) {logger.info("RestControllerController RuntimeException 异常->{}", e.getMessage());return "error";}
}
使用HandlerExceptionResolver接口实现处理全局异常
package com.example.exception;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;@Component
public class ExceptionResolver1 implements HandlerExceptionResolver {private static transient Logger logger = LoggerFactory.getLogger(ExceptionResolver1.class);@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {ModelAndView mv = new ModelAndView();logger.info("ExceptionResolver 异常->{}", ex.getMessage());return mv;}
}
package com.example.exception;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;@Component
public class ExceptionResolver2 implements HandlerExceptionResolver {private static transient Logger logger = LoggerFactory.getLogger(ExceptionResolver1.class);@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {ModelAndView mv = new ModelAndView();logger.info("ExceptionResolver2 异常->{}", ex.getMessage());return mv;}
}
HandlerExceptionResolver接口是没有排序的,所以应用程序无法保证执行顺序
如果第一个执行的HandlerExceptionResolver已经返回了ModelAndView,那么后续所有的HandlerExceptionResolver都将不会被执行;返回null会使得后续HandlerExceptionResolver继续执行,一直到返回ModelAndView的HandlerExceptionResolver结束(如果你的所有实现全部返回null值也没有意义)
@ControllerAdvice (@RestControllerAdvice) 配合 @ExceptionHandler的全局异常处理会先于HandlerExceptionResolver执行,所以@ExceptionHandler注解方法中的异常可以由HandlerExceptionResolver处理(保证@ExceptionHandler注解方法不抛出异常才是正确的)
核心源码位置
Spring MVC在 DispatcherServlet#processHandlerException()方法中使用上述的异常解析器,关于异常处理返回值解析的问题以及异常处理顺序的问题都可以以该方法为起点打断点DEBUG
@Nullableprotected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;//就在这个位置if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {if (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}// We might still need view name translation for a plain error model...if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}if (logger.isTraceEnabled()) {logger.trace("Using resolved error view: " + exMv, ex);}else if (logger.isDebugEnabled()) {logger.debug("Using resolved error view: " + exMv);}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}throw ex;}
Spring MVC在WebMvcConfigurationSupport#handlerExceptionResolver()方法中处理异常解析器的注入,该方法返回的是一个包装器HandlerExceptionResolverComposite,该包装器持有HandlerExceptionResolver
@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}
WebMvcConfigurationSupport#configureHandlerExceptionResolvers()方法是一个空实现,需要子类实现去注入自定义的HandlerExceptionResolver
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
如果没有提供自定义的异常解析器,会默认使用WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers()方法加载异常解析器
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,ContentNegotiationManager mvcContentNegotiationManager) {ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}exceptionHandlerResolver.afterPropertiesSet();exceptionResolvers.add(exceptionHandlerResolver);ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);exceptionResolvers.add(new DefaultHandlerExceptionResolver());}
ExceptionHandlerExceptionResolver类的exceptionHandlerAdviceCache属性中存储的就是@ExceptionHandler注解的异常处理器,初始化核心代码
@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}if (logger.isDebugEnabled()) {int handlerSize = this.exceptionHandlerAdviceCache.size();int adviceSize = this.responseBodyAdvice.size();if (handlerSize == 0 && adviceSize == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " +handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");}}}
注意:自定义实现WebMvcConfigurationSupport可能会导致 @ExceptionHandler异常处理器失效,比如重写WebMvcConfigurationSupport#configureHandlerExceptionResolvers()方法并添加了自己的异常处理器
package com.example.config;import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Configuration
public class WebMvcConfigurationSupportTest extends WebMvcConfigurationSupport {@Overrideprotected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {exceptionResolvers.add(new HandlerExceptionResolver() {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) {System.out.println("resolveException.......................");return new ModelAndView();}});}
}