首页后端开发其他后端知识SpringBoot接口统一异常处理有什么操作方法

SpringBoot接口统一异常处理有什么操作方法

时间2024-03-24 23:46:03发布访客分类其他后端知识浏览653
导读:在实际案例的操作过程中,我们可能会遇到“SpringBoot接口统一异常处理有什么操作方法”这样的问题,那么我们该如何处理和解决这样的情况呢?这篇小编就给大家总结了一些方法,具有一定的借鉴价值,希望对大家有所帮助,接下来就让小编带领大家一起...
在实际案例的操作过程中,我们可能会遇到“SpringBoot接口统一异常处理有什么操作方法”这样的问题,那么我们该如何处理和解决这样的情况呢?这篇小编就给大家总结了一些方法,具有一定的借鉴价值,希望对大家有所帮助,接下来就让小编带领大家一起了解看看吧。
   


为什么要优雅的处理异常

如果我们不统一的处理异常,经常会在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 ResponseEntityString>
 add(@Valid @RequestBody UserParam userParam) {

        // 每个接口充斥着大量的异常处理
        try {

            // do something
        }
 catch(Exception e) {
    
            return ResponseEntity.fail("error");

        }
    
        return ResponseEntity.ok("success");

    }

}

那怎么实现统一的异常处理,特别是结合参数校验等封装?

实现案例

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

@ControllerAdvice异常统一处理

对于400参数错误异常

/**
 * Global exception handler.
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {


    /**
     * exception handler for bad request.
     *
     * @param e
     *            exception
     * @return ResponseResult
     */
    @ResponseBody
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = {
 BindException.class, ValidationException.class, MethodArgumentNotValidException.class }
    )
    public ResponseResultExceptionData>
 handleParameterVerificationException(@NonNull Exception e) {
    
        ExceptionData.ExceptionDataBuilder exceptionDataBuilder = ExceptionData.builder();

        log.warn("Exception: {
}
    ", e.getMessage());

        if (e instanceof BindException) {
    
            BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
    
            bindingResult.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .forEach(exceptionDataBuilder::error);

        }
 else if (e instanceof ConstraintViolationException) {

            if (e.getMessage() != 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 ResponseResultBusinessException>
 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 ResponseResultException>
 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 ResponseEntityUserParam>
 add(@Valid @RequestBody UserParam userParam) {
    
        return ResponseEntity.ok(userParam);

    }

}

运行测试

这里用postman测试下:

进一步理解

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

@ControllerAdvice还可以怎么用?

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

  • @InitBinder注解

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

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

@InitBinder
public 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 ResponseEntityString>
 saveSomeObj(@ModelAttribute("currentUser") UserDetails operator) {
    
    // 保存操作,并设置当前操作人员的ID(从UserDetails中获得)
    return ResponseEntity.success("ok");

}

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

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

/**
    * This implementation calls {
@link #initStrategies}
.
    */
@Override
protected 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.
        MapString, 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, we'll 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

@Override
public void afterPropertiesSet() {
    
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();


    if (this.argumentResolvers == null) {
    
        ListHandlerMethodArgumentResolver>
     resolvers = getDefaultArgumentResolvers();
    
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

    }

    if (this.initBinderArgumentResolvers == null) {
    
        ListHandlerMethodArgumentResolver>
     resolvers = getDefaultInitBinderArgumentResolvers();
    
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

    }

    if (this.returnValueHandlers == null) {
    
        ListHandlerMethodReturnValueHandler>
     handlers = getDefaultReturnValueHandlers();
    
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);

    }

}


private void initControllerAdviceCache() {

    if (getApplicationContext() == null) {
    
        return;

    }
    

    ListControllerAdviceBean>
     adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    

    ListObject>
     requestResponseBodyAdviceBeans = new ArrayList>
    ();


    for (ControllerAdviceBean adviceBean : adviceBeans) {
    
        Class?>
     beanType = adviceBean.getBeanType();

        if (beanType == null) {
    
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);

        }
    
        // 缓存所有modelAttribute注解方法
        SetMethod>
     attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);

        if (!attrMethods.isEmpty()) {
    
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);

        }
    
        // 缓存所有initBinder注解方法
        SetMethod>
     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.
        MapString, 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

@Override
public void afterPropertiesSet() {
    
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();


    if (this.argumentResolvers == null) {
    
        ListHandlerMethodArgumentResolver>
     resolvers = getDefaultArgumentResolvers();
    
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

    }

    if (this.returnValueHandlers == null) {
    
        ListHandlerMethodReturnValueHandler>
     handlers = getDefaultReturnValueHandlers();
    
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);

    }

}

private void initExceptionHandlerAdviceCache() {

    if (getApplicationContext() == null) {
    
        return;

    }
    
    ListControllerAdviceBean>
     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接口统一异常处理有什么操作方法”已经有了进一步的了解,更多相关的问题,欢迎关注网络或到官网咨询客服。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: SpringBoot接口统一异常处理有什么操作方法
本文地址: https://pptw.com/jishu/652398.html
PHP中删除文件函数有什么,怎么使用 PHP如何实现16进制转换36进制,使用什么方法

游客 回复需填写必要信息