springboot 异常统一处理,springboot异步接口响应

  springboot 异常统一处理,springboot异步接口响应

  

目录

为什么要优雅的处理异常实现案例@ControllerAdvice异常统一处理控制器接口运行测试进一步理解@ControllerAdvice还可以怎么用?@ControllerAdvice是如何起作用的(原理)?

 

  

为什么要优雅的处理异常

如果我们不统一的处理异常,经常会在controller层有大量的异常处理的代码, 比如:

 

  @ Slf4j @ Api(value= User Interfaces ,tags= User Interfaces )@ rest controller @ request mapping(/User )公共类用户控制器{/* * * http://localhost 33608080/User/add .* * @ param User param User param * @ return User */@ API operation( Add User )@ apimplicitparam(name= User param ,type=body ,dataTypeClass=UserParam.class,required=true)@ post mapping( Add )公共响应实体字符串Add(@ Valid @ request body User param User param){//每个接口充斥着大量的异常处理try {//do something } catch(Exception e){ return response entity。失败(“错误”);} return ResponseEntity.ok(成功);}}那怎么实现统一的异常处理,特别是结合参数校验等封装?

  

实现案例

简单展示通过@ControllerAdvice进行统一异常处理。

 

  

@ControllerAdvice异常统一处理

对于400参数错误异常

 

  /** *全局异常处理程序. class */@ SLF 4j @ restcontrolleradvice public类GlobalExceptionHandler { /** *错误请求的异常处理程序. * * @ param e * exception * @返回响应结果*/@ response body @ response status(code=http status .BAD _ REQUEST)@异常处理程序(value={ bind Exception。class,ValidationException.class,methodgargumentnotvalidalexception。class })公共responseresultexception数据句柄parametercverificationexception(@ NonNull Exception e){异常数据.异常数据生成器异常数据生成器=异常数据。builder();log.warn(Exception: {} ,e . getmessage());if(e bind exception的实例){ binding result binding result=((methoargumentnotvaliexception)e).get binding result();bindingResult.getAllErrors().流()。map(defaultmessagesourceresolvable :3360 getdefaultmessage).forEach(exception data builder :错误);} else if(ConstraintViolationException的实例){ if(例如

  etMessage() != null) { exceptionDataBuilder.error(e.getMessage()); } } else { exceptionDataBuilder.error("invalid parameter"); } return ResponseResultEntity.fail(exceptionDataBuilder.build(), "invalid parameter"); }}对于自定义异常

  

/** * handle business exception. * * @param businessException * business exception * @return ResponseResult */@ResponseBody@ExceptionHandler(BusinessException.class)public ResponseResult<BusinessException> processBusinessException(BusinessException businessException) { log.error(businessException.getLocalizedMessage(), businessException); // 这里可以屏蔽掉后台的异常栈信息,直接返回"business error" return ResponseResultEntity.fail(businessException, businessException.getLocalizedMessage());}

对于其它异常

 

  

/** * handle other exception. * * @param exception * exception * @return ResponseResult */@ResponseBody@ExceptionHandler(Exception.class)public ResponseResult<Exception> processException(Exception exception) { log.error(exception.getLocalizedMessage(), exception); // 这里可以屏蔽掉后台的异常栈信息,直接返回"server error" return ResponseResultEntity.fail(exception, exception.getLocalizedMessage());}

 

  

Controller接口

(接口中无需处理异常)

 

  

@Slf4j@Api(value = "User Interfaces", tags = "User Interfaces")@RestController@RequestMapping("/user")public class UserController { /** * http://localhost:8080/user/add . * * @param userParam user param * @return user */ @ApiOperation("Add User") @ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true) @PostMapping("add") public ResponseEntity<UserParam> add(@Valid @RequestBody UserParam userParam) { return ResponseEntity.ok(userParam); }}

 

  

运行测试

这里用postman测试下:

 

  

 

  

 

  

进一步理解

我们再通过一些问题来帮助你更深入理解

 

  

 

  

@ControllerAdvice还可以怎么用?

除了通过@ExceptionHandler注解用于全局异常的处理之外,@ControllerAdvice还有两个用法:

 

  @InitBinder注解用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;

  比如,在@ControllerAdvice注解的类中添加如下方法,来统一处理日期格式的格式化

  

@InitBinderpublic void handleInitBinder(WebDataBinder dataBinder){ dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));}

Controller中传入参数(string类型)自动转化为Date类型

 

  

@GetMapping("testDate")public Date processApi(Date date) { return date;}

@ModelAttribute注解用来预设全局参数,比如最典型的使用Spring Security时将添加当前登录的用户信息(UserDetails)作为参数。

 

  

@ModelAttribute("currentUser")public UserDetails modelAttribute() { return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();}

所有controller类中requestMapping方法都可以直接获取并使用currentUser

 

  

@PostMapping("saveSomething")public ResponseEntity<String> saveSomeObj(@ModelAttribute("currentUser") UserDetails operator) { // 保存操作,并设置当前操作人员的ID(从UserDetails中获得) return ResponseEntity.success("ok");}

 

  

@ControllerAdvice是如何起作用的(原理)?

DispatcherServlet中onRefresh方法是初始化ApplicationContext后的回调方法,它会调用initStrategies方法,主要更新一些servlet需要使用的对象,包括国际化处理,requestMapping,视图解析等等。

 

  

/** * This implementation calls {@link #initStrategies}. */@Overrideprotected void onRefresh(ApplicationContext context) { initStrategies(context);}/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); // 文件上传 initLocaleResolver(context); // i18n国际化 initThemeResolver(context); // 主题 initHandlerMappings(context); // requestMapping initHandlerAdapters(context); // adapters initHandlerExceptionResolvers(context); // 异常处理 initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context);}

从上述代码看,如果要提供@ControllerAdvice提供的三种注解功能,从设计和实现的角度肯定是实现的代码需要放在initStrategies方法中。

 

  @ModelAttribute和@InitBinder处理具体来看,如果你是设计者,很显然容易想到:对于@ModelAttribute提供的参数预置和@InitBinder注解提供的预处理方法应该是放在一个方法中的,因为它们都是在进入requestMapping方法前做的操作。

  如下方法是获取所有的HandlerAdapter,无非就是从BeanFactory中获取

  

private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); // We keep HandlerAdapters in sorted order. AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { // Ignore, well add a default HandlerAdapter later. } } // Ensure we have at least some HandlerAdapters, by registering // default HandlerAdapters if no other adapters are found. if (this.handlerAdapters == null) { this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerAdapters declared for servlet " + getServletName() + ": using default strategies from DispatcherServlet.properties"); } }}

我们要处理的是requestMapping的handlerResolver,作为设计者,就很容易出如下的结构

 

  

 

  在RequestMappingHandlerAdapter中的afterPropertiesSet去处理advice

  

@Overridepublic void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { 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; } List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); List<Object> requestResponseBodyAdviceBeans = new ArrayList<>(); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } // 缓存所有modelAttribute注解方法 Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(adviceBean, attrMethods); } // 缓存所有initBinder注解方法 Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this.initBinderAdviceCache.put(adviceBean, binderMethods); } if (RequestBodyAdvice.class.isAssignableFrom(beanType) ResponseBodyAdvice.class.isAssignableFrom(beanType)) { requestResponseBodyAdviceBeans.add(adviceBean); } } if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); }}

@ExceptionHandler处理@ExceptionHandler显然是在上述initHandlerExceptionResolvers(context)方法中。

 

  同样的,从BeanFactory中获取HandlerExceptionResolver

  

/** * Initialize the HandlerExceptionResolver used by this class. * <p>If no bean is defined with the given name in the BeanFactory for this namespace, * we default to no exception resolver. */private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); // We keep HandlerExceptionResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } else { try { HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } catch (NoSuchBeanDefinitionException ex) { // Ignore, no HandlerExceptionResolver is fine too. } } // Ensure we have at least some HandlerExceptionResolvers, by registering // default HandlerExceptionResolvers if no other resolvers are found. if (this.handlerExceptionResolvers == null) { this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerExceptionResolvers declared in servlet " + getServletName() + ": using default strategies from DispatcherServlet.properties"); } }}

我们很容易找到ExceptionHandlerExceptionResolver

 

  

 

  同样的在afterPropertiesSet去处理advice

  

@Overridepublic void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); 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); } }}

到此这篇关于SpringBoot接口如何统一异常处理的文章就介绍到这了,更多相关SpringBoot接口异常处理内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: