Spring 的MethodArgumentTypeMismatchException 和 MissingServletRequestParameterException 异常处理

Spring 有几个异常是在通过 url path 访问 Controller 方法时,由于参数不匹配而抛出的,比如 MethodArgumentTypeMismatchExceptionMissingServletRequestParameterException 异常。由于抛出该异常的时候还没有进入 path 指定的方法,因此方法内的 try...catch 是无法捕获的。如果要对它们进行捕获,有两种解决方案。

通过 ExceptionHandler 进行捕获

ExceptionHandler 可以指定对某个类型的异常进行定制化的处理:

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public void handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
    String name = ex.getName();
    String type = ex.getRequiredType().getSimpleName();
    Object value = ex.getValue();
    String message = String.format("'%s' should be a valid '%s' and '%s' isn't",name, type, value);
    // Do the graceful handling
}

ExceptionHandler 可以放在某个指定的 Controller 中,也可以放在一个 @ControllerAdvice 注解的类中对所有的 Controller 生效。

通过 HandlerExceptionResolver 进行捕获

有些时候不方便通过 ExceptionHandler 进行捕获,比如由于团队框架的原因继承了某个 BaseController,而 BaseController 中又被定义了 Throwable 级的处理(没错,很蠢的设计)。此时就需要更深一层的定制,即 HandlerExceptionResolver

这里要先介绍下 Spring 处理异常的逻辑。Spring 在捕获到未处理的异常时,会通过一个 HandlerExceptionResolver 的列表,依次调用其中的每个元素的 resolveException 方法,如果返回 null,则会继续下一个元素的进行调用,不为 null 即终止。其实上面的 ExceptionHandler 也是被 Spring 自己的一个通用 HandlerExceptionResolver 所使用。

因此自定义的 HandlerExceptionResolver 要加到 resolvers 的列表开头,优先于通用的 HandlerExceptionResolver 进行调用:

package com.xxx;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class RequestParameterExceptionHandler implements HandlerExceptionResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(RequestParameterExceptionHandler.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException e = (MissingServletRequestParameterException) ex;
            String body = String.format("缺少 %s 类型的参数 %s", e.getParameterType(), e.getParameterName());

            try {
                response.getWriter().write(body);
            } catch (IOException ioException) {
                LOGGER.error("{} 发生异常。", LOG_PREFIX, ioException);
            }

            return new ModelAndView();
        } else if (ex instanceof MethodArgumentTypeMismatchException) {
            MethodArgumentTypeMismatchException e = (MethodArgumentTypeMismatchException) ex;
            String body = String.format("%s 应该是 %s 类型", e.getName(), e.getRequiredType().getSimpleName()));

            try {
                response.getWriter().write(body);
            } catch (IOException ioException) {
                LOGGER.error("{} 发生异常。", LOG_PREFIX, ioException);
            }
            return new ModelAndView();
        }
        return null;
    }
}

接下来要注册到 Mvc:

package com.xxx;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(0, new RequestParameterExceptionHandler());
    }
}

这样就可以了。

参考:

posted @ 2022-03-04 21:30  青石向晚  阅读(1662)  评论(0编辑  收藏  举报