Spring MVC(4)-@RestControllerAdvice注解

news/2024/11/19 23:15:51/

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进行实例化:

@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!'}

http://www.ppmy.cn/news/50966.html

相关文章

【BIM+GIS】ArcGIS Pro3.0打开多种格式三维模型案例教程

本文讲解在ArcGIS Pro3.0打开BIM模型(.rvt)、倾斜模型OSGB、Sketchup(.skp)、3d max(.3ds)、点云数据(.las)的方法及注意事项。 文章目录 一、ArcGIS Pro打开BIM(.rvt)二、ArcGIS Pro打开倾斜OSGB三、ArcGIS Pro打开Sketchup(.skp)四、ArcGIS Pro打开3d max(.3ds)…

如何保护数据安全?企业该从部署SSL证书开始

数字化时代&#xff0c;大数据开始蔓延到各行各业&#xff0c;影响着生活的方方面面。在犹如“皇帝穿新衣”般透明的大数据时代&#xff0c;数据泄露无孔不入&#xff0c;存在问题层出不穷&#xff0c;未知的漏洞隐患、安全边界的模糊、新的网络攻击手段、个人隐私的无处藏身等…

动态规划猜法中外部信息简化的相关问题(上)

文章目录 1、Leetcode 312.戳气球&#xff08;困难&#xff09;1.1 题目描述1.2 思路分析1.3 代码实现1.4 启示 2、Leetcode 546.移除盒子&#xff08;困难&#xff09;2.1 题目描述2.2 思路分析2.3 代码实现 3、消除字符3.1 题目描述3.2 思路分析3.3 代码实现 1、Leetcode 312…

自有品牌与新兴渠道双轮驱动,丽人丽妆提速起航

2023年4月12日&#xff0c;上海市电子商务行业协会评选出上海市数字商务优秀企业&#xff0c;丽人丽妆凭借在数智化营销领域的专业能力&#xff0c;荣获“上海市数字商务优秀企业”称号。 此次获奖&#xff0c;也反映了丽人丽妆以科技赋能企业高效运营&#xff0c;已经取得突出…

再捐1亿元种树治沙:蚂蚁集团持续七年支持内蒙古生态治理

今天&#xff08;4月22日&#xff09;是“世界地球日”&#xff0c;内蒙古自治区林草局与蚂蚁集团启动战略合作&#xff1a;由蚂蚁集团在三年内再捐资1亿元&#xff0c;通过公益项目“蚂蚁森林”支持浑善达克沙地的生态治理。这1亿元将用于当地林草生态的修复保护、沙化土地的治…

怎么样才能在Python中确保对象只能一个被实例化

怎么样才能在Python中确保对象只能一个被实例化 在许多软件设计场景中&#xff0c;我们希望确保一个类的对象只能被实例化一次。这种设计模式被称为单例模式&#xff08;Singleton Pattern&#xff09;。本文将详细介绍如何在Python中实现单例模式。 什么是单例模式&#xff…

使用优先队列解决自己构造的数据类型

在C中优先队列有两种&#xff0c;最大堆和最小堆。当数据类型为int的时候&#xff0c;大家都会使用&#xff0c;但是如果数据不是单一的&#xff0c;比如数据是一个hashmap怎么办&#xff1f;例子如下&#xff1a; You are given an array of strings names, and an array hei…

在金融领域使用机器学习的 9个技巧

机器学习已经倍证明可以预测结果和发掘隐藏的数据模式。但是必须小心使用&#xff0c;并遵循一些规则&#xff0c;否则就会在数据的荒野中徘徊而无所获。使用机器学习进行交易的道路充满了陷阱和挑战&#xff0c;只有那些勤奋认真地遵循规则的人才能从中获得收益。下面是一些技…