您现在的位置是:首页 >学无止境 >Spring MVC(4)-@RestControllerAdvice注解网站首页学无止境
Spring MVC(4)-@RestControllerAdvice注解
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
进行实例化:
@Bean
public 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方法
@Override
public void afterPropertiesSet() {
// TODO 在第一次执行时,会初始化ControllerAdvice缓存。
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
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 bean
List<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,将其登记到 requestResponseBodyAdviceBeans
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
// 如果当前 ControllerAdviceBean 继承自 ResponseBodyAdvice,将其登记到 requestResponseBodyAdviceBeans
if (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
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.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 well
exceptionHandlerMethod.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!'}