【问题标题】:Spring Boot - handle exception wrapped with BindExceptionSpring Boot - 处理用 BindException 包装的异常
【发布时间】:2023-04-10 14:46:05
【问题描述】:

我正在寻找一种方法来处理在将请求参数绑定到 DTO 字段期间引发的自定义异常。

我在 Spring Boot 应用程序中有一个控制器,如下所示

@GetMapping("/some/url")
public OutputDTO filterEntities(InputDTO inputDTO) {
    return service.getOutput(inputDTO);
}

输入DTO的字段很少,其中一个是枚举类型

public class InputDTO {

    private EnumClass enumField;
    private String otherField;

    /**
     * more fields
     */
}

用户会以这种方式点击网址

localhost:8081/some/url?enumField=wrongValue&otherField=anyValue

现在,如果用户为 enumField 发送了错误的值,我想抛出带有特定消息的 CustomException。在 binder 中实现 enum 实例创建和抛出异常的过程

@InitBinder
public void initEnumClassBinder(final WebDataBinder webdataBinder) {
    webdataBinder.registerCustomEditor(
            EnumClass.class,
            new PropertyEditorSupport() {
                @Override
                public void setAsText(final String text) throws IllegalArgumentException {
                    try {
                        setValue(EnumClass.valueOf(text.toUpperCase()));
                    } catch (Exception exception) {
                        throw new CustomException("Exception while deserializing EnumClass from " + text, exception);
                    }
                }
            }
    );
}

问题是当抛出异常时无法处理

@ExceptionHandler(CustomException.class)
public String handleException(CustomException exception) {
    // log exception
    return exception.getMessage();
}

Spring 使用 BindException 包装初始异常。该实例包含我的初始错误消息,但与其他对我来说多余的文本连接在一起。我不认为解析和子串该消息是好的......

我错过了什么吗?从初始获取消息的正确方法是什么 这里有自定义异常?

【问题讨论】:

标签: java spring-boot exception-handling


【解决方案1】:

您将无法使用 @ExceptionHandler 带注释的方法处理在进入控制器方法之前引发的异常。 Spring 通过注册DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver 处理程序在进入控制器之前处理这些异常。 这是 BindingException 的情况,当 Spring 无法绑定请求参数以匹配您的 InputDTO 对象时抛出。 您可以做的是注册自己的处理程序(创建一个实现HandlerExceptionResolver 和Ordered 接口的Component),在处理错误时给予它最高优先级,并根据需要处理异常。 您还必须注意BindException,因为它包装了您的自定义异常CustomException.class

import java.io.IOException;

import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger; import org.slf4j.LoggerFactory; 
import org.springframework.core.Ordered; 
import org.springframework.stereotype.Component; 
import org.springframework.validation.BindException; 
import org.springframework.validation.ObjectError; 
import org.springframework.web.servlet.HandlerExceptionResolver; 
import org.springframework.web.servlet.ModelAndView;


import yourpackage.CustomException;

@Component() 
public class BindingExceptionResolver implements HandlerExceptionResolver, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(BindingExceptionResolver.class);

    public BindingExceptionResolver() {
    }

    private ModelAndView handleException(ObjectError objectError, HttpServletResponse response){
        if (objectError == null) return null;
        try {
            if(objectError.contains(CustomException.class)) {
                CustomException ex = objectError.unwrap(CustomException.class);
                logger.error(ex.getMessage(), ex);
                return handleCustomException(ex, response);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    protected ModelAndView handleCustomException(CustomException ex, HttpServletResponse response) throws IOException {
        response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
        return new ModelAndView();
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof org.springframework.validation.BindException) {
                BindException be = (BindException) ex;
                logger.debug("Binding exception in {} :: ({}) :: ({})=({})", be.getObjectName(), be.getBindingResult().getTarget().getClass(), be.getFieldError().getField(), be.getFieldError().getRejectedValue());
                return be.getAllErrors().stream()
                    .filter(o->o.contains(Exception.class))
                    .map(o ->handleException(o, response))
                    .filter(mv ->mv !=null)
                    .findFirst().orElse(null);
            }
        } catch (Exception handlerException) {
            logger.error("Could not handle exception", handlerException); 
        }
        return null;
    }


    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

}

希望对你有帮助

【讨论】:

  • 非常感谢@bogdotro。你的解释很有帮助。
猜你喜欢
  • 2023-03-28
  • 2013-03-09
  • 2015-02-23
  • 2016-09-29
  • 1970-01-01
  • 2022-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多