Spring MVC(3)-MVC执行流程分析中介绍MVC执行的流程,在DispatcherServlet#processDispatchResult
处理结果时,如果出现异常执行processHandlerException
方法,也就是异常的处理,便使用到了@RestControllerAdvice
注解定义的异常处理。
@RestControllerAdvice
@RestControllerAdvice
是一个组合注解,由@ControllerAdvice
、@ResponseBody
组成。 携带此注解的类型被视为控制器advice,其中@ExceptionHandler
方法默认采用@ResponseBody
语义。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {/**哪些package下的Controller使用RestControllerAdvice */@AliasFor("basePackages")String[] value() default {};/**哪些package下的Controller使用RestControllerAdvice */@AliasFor("value")String[] basePackages() default {};/**basePackages下指定class使用RestControllerAdvice ,class通常是一个特殊的无操作标记类或接口 */Class<?>[] basePackageClasses() default {};/**指定class使用RestControllerAdvice*/Class<?>[] assignableTypes() default {};/**指定Annotation标注的Controller使用RestControllerAdvice*/Class<? extends Annotation>[] annotations() default {};
}
Spring MVC 默认配置RequestMappingHandlerAdapter
处理@RestControllerAdvice
注解。
RequestMappingHandlerAdapter
介绍
RequestMappingHandlerAdapter
是处理Controller的HandlerAdapter
实现类。
它还有一个抽象父类AbstractHandlerMethodAdapter,顾名思义,是专门用来处理HandlerMethod类型的handler。具体可以看AbstractHandlerMethodAdapter#supports
方法:
public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
配套使用:
RequestMappingHandlerMapping
负责根据request找到映射的handler;RequestMappingHandlerAdapter
负责执行handler对应的方法,包括参数解析、校验参数等;
核心字段
argumentResolvers
:参数解析器,RequestMappingHandlerAdapter
使用argumentResolvers
进行参数解析,例如常见的@RequestParam
、@RequestBody
等。customArgumentResolvers
:用于缓存开发人员自定义的参数解析器,即通过WebMvcConfigurer#addArgumentResolvers()
方法添加的解析器。returnValueHandlers
:返回值处理器,它可以对控制层业务返回值进行处理。例如对@ResponseBody
标注的返回值进行JSON格式化,并写到输出流。
实例化
WebMvcConfigurationSupport#requestMappingHandlerMapping
对RequestMappingHandlerAdapter
进行实例化:
@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping() {// RequestMappingHandlerMapping 实现了 InitializingBean,所以它在实例化后,会调用 afterPropertiesSet() 进行初始化。// 创建 RequestMappingHandlerMapping。RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();mapping.setOrder(0);// getInterceptors()中调用了钩子方法,可通过 WebMvcConfigurer进行扩展mapping.setInterceptors(getInterceptors());mapping.setContentNegotiationManager(mvcContentNegotiationManager());// getCorsConfigurations() 调用了钩子犯法mapping.setCorsConfigurations(getCorsConfigurations());PathMatchConfigurer configurer = getPathMatchConfigurer();Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();if (useSuffixPatternMatch != null) {mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);}Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();if (useRegisteredSuffixPatternMatch != null) {mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);}Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();if (useTrailingSlashMatch != null) {mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);}UrlPathHelper pathHelper = configurer.getUrlPathHelper();if (pathHelper != null) {mapping.setUrlPathHelper(pathHelper);}PathMatcher pathMatcher = configurer.getPathMatcher();if (pathMatcher != null) {mapping.setPathMatcher(pathMatcher);}Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();if (pathPrefixes != null) {mapping.setPathPrefixes(pathPrefixes);}return mapping;}
afterPropertiesSet方法
@Overridepublic void afterPropertiesSet() {// TODO 在第一次执行时,会初始化ControllerAdvice缓存。// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {// TODO 参数解析器,里面注册了很多的参数解析类。实际上,参数的解析是一个十分复杂的过程,它的可能情况太多了。List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();// 将所有的参数解析器封装到 argumentResolvers 中。this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {// initBinder 参数解析器List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {// 方法返回值处理类注册List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}// TODO 全局异常处理的Bean扫描,@ControllerAdvice 注解的bean会被扫描到。List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());AnnotationAwareOrderComparator.sort(adviceBeans);// this.requestResponseBodyAdvice :// 用于记录所有 @ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean// this.modelAttributeAdviceCache :// 用于记录所有 @ControllerAdvice bean组件中的 @ModuleAttribute 方法// this.initBinderAdviceCache :// 用于记录所有 @ControllerAdvice bean组件中的 @InitBinder 方法// 用于临时记录所有 @ControllerAdvice + RequestResponseBodyAdvice beanList<Object> requestResponseBodyAdviceBeans = new ArrayList<>();// 遍历每个使用了注解 @ControllerAdvice 的 bean 组件for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 方法过滤器 @RequestMapping && @ModelAttribute 注解扫描Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {// 封装adviceBean和 @ModelAttribute 注解方法的映射关系。this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}// 方法过滤器 InitBinder 扫描Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {// 封装 adviceBean 和 binderMethods 的映射关系this.initBinderAdviceCache.put(adviceBean, binderMethods);}// 如果当前 ControllerAdviceBean 继承自 RequestBodyAdvice,将其登记到 requestResponseBodyAdviceBeansif (RequestBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}// 如果当前 ControllerAdviceBean 继承自 ResponseBodyAdvice,将其登记到 requestResponseBodyAdviceBeansif (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}}if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}if (logger.isDebugEnabled()) {int modelSize = this.modelAttributeAdviceCache.size();int binderSize = this.initBinderAdviceCache.size();int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");}}}
@RestControllerAdvice和@ExceptionHandler组合使用
在MVC处理流程中,DispatcherServlet#processHandlerException
异常处理,如果出现异常并且异常不是ModelAndViewDefiningException
,那么将执行@RestControllerAdvice
对于定义的异常的处理。
DispatcherServlet#processHandlerException
@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) {//@ControllerAdvise标注的使用 HandlerExceptionResolverComposite 处理for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// TODO 异常处理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);}if (logger.isDebugEnabled()) {logger.debug("Using resolved error view: " + exMv);}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}throw ex;}/**AbstractHandlerExceptionResolver#resolveException*/public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {//判断当前Request和handler是否支持if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);// TODO 处理异常ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// Print warn message when warn logger is not enabled...if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}// warnLogger with full stack trace (requires explicit config)logException(ex, request);}return result;}else {return null;}}/**AbstractHandlerMethodExceptionResolver#doResolveException*/protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {// TODO 执行异常处理return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);}/**ExceptionHandlerExceptionResolver#doResolveHandlerMethodException*/protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {// TODO 获取异常处理方法ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}// 设置参数解析器和返回值处理器if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();try {if (logger.isDebugEnabled()) {logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);}Throwable cause = exception.getCause();if (cause != null) {// 异常处理方法的执行。// Expose cause as provided argument as wellexceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {// 请求@ExceptionHandler标注的方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);}}catch (Throwable invocationEx) {// Any other than the original exception is unintended here,// probably an accident (e.g. failed assertion or the like).if (invocationEx != exception && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}if (mavContainer.isRequestHandled()) {return new ModelAndView();}else {ModelMap model = mavContainer.getModel();HttpStatus status = mavContainer.getStatus();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);mav.setViewName(mavContainer.getViewName());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}return mav;}}
定义Controller
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/getStatus")public String getStatus(@RequestParam(required = false) Integer status) {Assert.notNull(status, "status is null!");if (Objects.equals(status, 0)) {return "正常";} else {return "异常";}}
}
定义异常处理类
/**这里只处理UserController*/
@RestControllerAdvice(assignableTypes = UserController.class)
public class UserControllerAdviceController {@ExceptionHandler(value = {RuntimeException.class})public String runtimeException(Exception e){return String.format("{'code':-1,'msg':'%s'}",e.getMessage());}
}
请求与结果
GET http://localhost:21004/user/getStatus//返回
{'code':-1,'msg':'status is null!'}