您现在的位置是:首页 >学无止境 >Spring MVC(4)-@RestControllerAdvice注解网站首页学无止境

Spring MVC(4)-@RestControllerAdvice注解

冲上云霄的Jayden 2023-05-28 00:00:03
简介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));  
}

RequestMappingHandlerMappingRequestMappingHandlerAdapter配套使用:

  1. RequestMappingHandlerMapping负责根据request找到映射的handler;
  2. RequestMappingHandlerAdapter负责执行handler对应的方法,包括参数解析、校验参数等;

核心字段

  1. argumentResolvers:参数解析器,RequestMappingHandlerAdapter使用argumentResolvers进行参数解析,例如常见的@RequestParam@RequestBody等。
  2. customArgumentResolvers:用于缓存开发人员自定义的参数解析器,即通过WebMvcConfigurer#addArgumentResolvers()方法添加的解析器。
  3. returnValueHandlers:返回值处理器,它可以对控制层业务返回值进行处理。例如对@ResponseBody标注的返回值进行JSON格式化,并写到输出流。

实例化

WebMvcConfigurationSupport#requestMappingHandlerMappingRequestMappingHandlerAdapter进行实例化:

@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!'}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。