自动配置
核心类的代码
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {//调用 添加ExceptionHandlerExceptionResolveraddDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}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());}}public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {@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);}}...}}
我们的代码
@RestControllerAdvice@Slf4jpublic class MyExceptionHandler {@ExceptionHandler(value = BusException.class)@ResponseStatus(HttpStatus.FORBIDDEN)public String bus(BusException ex){log.error(ex.toString());return ex.getMessage();}}
ServletInvocableHandlerMethod构造器调用链,其中就会去判断HTTP返回码,根据上面的@ResponseStatus(HttpStatus.FORBIDDEN)去取状态码和原因
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {//1public ServletInvocableHandlerMethod(Object handler, Method method) {super(handler, method);}//2public InvocableHandlerMethod(Object bean, Method method) {super(bean, method);}//3public HandlerMethod(Object bean, Method method) {Assert.notNull(bean, "Bean is required");Assert.notNull(method, "Method is required");this.bean = bean;this.beanFactory = null;this.beanType = ClassUtils.getUserClass(bean);this.method = method;this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);this.parameters = initMethodParameters();evaluateResponseStatus();this.description = initDescription(this.beanType, this.method);}//4private void evaluateResponseStatus() {ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);if (annotation == null) {annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);}if (annotation != null) {this.responseStatus = annotation.code();this.responseStatusReason = annotation.reason();}}}
我们的Bean
@ResponseStatus(code = HttpStatus.GATEWAY_TIMEOUT,reason = "my gateway")public class MyResStatus extends RuntimeException {public MyResStatus() {}public MyResStatus(String message) {super(message);}}
controller
//对应我们自定义的运行时异常@GetMapping("/resstatus11")@ResponseBodypublic MyResStatus resSta11(){throw new MyResStatus("nono11");}//直接用spring的异常@GetMapping("/resstatus3")@ResponseBodypublic String resSta3() {throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY,"asdasdas");}
spring如何解析
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {try {//逻辑1 判断异常if (ex instanceof ResponseStatusException) {return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);}//逻辑2 判断异常有这个注解ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);if (status != null) {return resolveResponseStatus(status, request, response, handler, ex);}//逻辑3 取出套娃里面的异常if (ex.getCause() instanceof Exception) {return doResolveException(request, response, handler, (Exception) ex.getCause());}}catch (Exception resolveEx) {if (logger.isWarnEnabled()) {logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);}}return null;}}
我们的代码
@GetMapping("/defaulte")public String defaulte() throws HttpRequestMethodNotSupportedException {throw new HttpRequestMethodNotSupportedException("GET");}
解析代码
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {//逻辑就是只处理一些特定的异常//这个是兜底处理的,最后轮到他,最终会调用response.sendError 并且返回空ModelAndView@Override@Nullableprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {try {if (ex instanceof HttpRequestMethodNotSupportedException) {return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, response, handler);}else if (ex instanceof HttpMediaTypeNotSupportedException) {return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response, handler);}else if (ex instanceof HttpMediaTypeNotAcceptableException) {return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response, handler);}else if (ex instanceof MissingPathVariableException) {return handleMissingPathVariable((MissingPathVariableException) ex, request, response, handler);}else if (ex instanceof MissingServletRequestParameterException) {return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, response, handler);}else if (ex instanceof ServletRequestBindingException) {return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response, handler);}else if (ex instanceof ConversionNotSupportedException) {return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);}else if (ex instanceof TypeMismatchException) {return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);}else if (ex instanceof HttpMessageNotReadableException) {return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);}else if (ex instanceof HttpMessageNotWritableException) {return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);}else if (ex instanceof MethodArgumentNotValidException) {return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);}else if (ex instanceof MissingServletRequestPartException) {return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);}else if (ex instanceof BindException) {return handleBindException((BindException) ex, request, response, handler);}else if (ex instanceof NoHandlerFoundException) {return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);}else if (ex instanceof AsyncRequestTimeoutException) {return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, request, response, handler);}}catch (Exception handlerEx) {if (logger.isWarnEnabled()) {logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);}}return null;}//随便找一个举例protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {String[] supportedMethods = ex.getSupportedMethods();if (supportedMethods != null) {response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));}response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());return new ModelAndView();}}
主要的类图

如果能处理,就返回视图,如果不能处理,就抛出异常
实现了Ordered接口,说明有顺序
2.1 2.2 2.3的添加位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers
1是自动配置注册的(ErrorMVCAutoConfiguration里面)
1作用,添加attribute,永远不返回视图,返回null
2.1结合注解使用
2.2配合异常使用
2.3未知
2.4可以自定义处理流程并且可以指定执行顺序
整体流程
扩展点
自定义HandlerExceptionResolver并制定Order
自定义ErrorViewResolver
最佳实践
常用的就是
@RestControllerAdvice+@ExceptionHandler(value = BusException.class)+@ResponseStatus(HttpStatus.FORBIDDEN)
前后端分离json传递并且是restful风格http状态码
