SpringBoot - 异常处理原理之优化参数验证

上次大概跟踪了下SpringBoot抛异常和异常截获的原理(https://blog.csdn.net/qq_27062249/article/details/118316320),中间还有一个问题,就是在SpringBoot解析异常的时候还有一个DefaultHandlerExceptionResolver,这个错误解析器就在解析器数组的index=2的位置,也就是前面两个错误解析器处理了的话,就会返回不会轮到跑DefaultHandlerExceptionResolver的处理逻辑了

DefaultHandlerExceptionResolver会处理那些出现的异常呢?都在DefaultHandlerExceptionResolver的doResolveException( )方法里,这里有HttpRequestMethodNotSupportedException(目标方法是一个get方法的话,当客户端发送的是post方法,这是就会抛出这个异常)、HttpMediaTypeNotSupportedException(比如方法上名明确的标注了接受json格式的参数客户端传的formData的格式,就会抛出这个异常)、NoHandlerFoundException(url路径输错时候出现的404)、MissingServletRequestPartException(服务端必传的参数,前端没传时候抛出的400)、MethodArgumentNotValidException(使用validation-api校验框架没有没通过校验时候抛出的异常。最后使用这个优化实际案例)等等

protected ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		try {
			if (ex instanceof HttpRequestMethodNotSupportedException) {
				return handleHttpRequestMethodNotSupported(
						(HttpRequestMethodNotSupportedException) ex, request, response, handler);
			}
			else if (ex instanceof HttpMediaTypeNotSupportedException) {
				return handleHttpMediaTypeNotSupported(
						(HttpMediaTypeNotSupportedException) ex, request, response, handler);
			}
			else if (ex instanceof HttpMediaTypeNotAcceptableException) {
				return handleHttpMediaTypeNotAcceptable(
						(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
			}
			else if (ex instanceof MissingPathVariableException) {
				return handleMissingPathVariable(
						(MissingPathVariableException) ex, request, response, handler);
			}
			else if (ex instanceof MissingServletRequestParameterException) {
				return handleMissingServletRequestParameter(
						(MissingServletRequestParameterException) ex, request, response, handler);
			}
			else if (ex instanceof ServletRequestBindingException) {
				return handleServletRequestBindingException(
						(ServletRequestBindingException) ex, request, response, handler);
			}
			else if (ex instanceof ConversionNotSupportedException) {
				return handleConversionNotSupported(
						(ConversionNotSupportedException) ex, request, response, handler);
			}
			else if (ex instanceof TypeMismatchException) {
				return handleTypeMismatch(
						(TypeMismatchException) ex, request, response, handler);
			}
			else if (ex instanceof HttpMessageNotReadableException) {
				return handleHttpMessageNotReadable(
						(HttpMessageNotReadableException) ex, request, response, handler);
			}
			else if (ex instanceof HttpMessageNotWritableException) {
				return handleHttpMessageNotWritable(
						(HttpMessageNotWritableException) ex, request, response, handler);
			}
			else if (ex instanceof MethodArgumentNotValidException) {
				return handleMethodArgumentNotValidException(
						(MethodArgumentNotValidException) ex, request, response, handler);
			}
			else if (ex instanceof MissingServletRequestPartException) {
				return handleMissingServletRequestPartException(
						(MissingServletRequestPartException) ex, request, response, handler);
			}
			else if (ex instanceof BindException) {
				return handleBindException((BindException) ex, request, response, handler);
			}
			else if (ex instanceof NoHandlerFoundException) {
				return handleNoHandlerFoundException(
						(NoHandlerFoundException) ex, request, response, handler);
			}
			else if (ex instanceof AsyncRequestTimeoutException) {
				return handleAsyncRequestTimeoutException(
						(AsyncRequestTimeoutException) ex, request, response, handler);
			}
		}
		catch (Exception handlerEx) {
			if (logger.isWarnEnabled()) {
				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
			}
		}
		return null;
	}

真实案例

清楚了上面SpringBoot处理系统默认异常的方式,结合平常开发时请求参数的判空等一些案例,我们来修改的更加方便一点

        参数判空的实际案例(这个是我上一家公司真实的项目中截取过来的。这些会不会很累?每个方法都需要判断,如果需要修改的话每一个方法都要修改一遍

    /**
     * 获取绘本在线播放资源 url
     *
     * @param mac       设备mac地址
     * @param bookId    绘本id
     * @param readModel 阅读模式:1->领读,2->指读
     * @param fileName  文件名
     * @return
     */
    @PostMapping("/query-single")
    public Result query(String mac, Long bookId, Integer readModel, String fileName) {
        Assert.isTrue(mac != null, "mac不能为空!!!");
        Assert.isTrue(bookId != null, "bookId不能为空!!!");
        Assert.isTrue(readModel != null, "阅读模式不能为空:1为领读,2为指读");
        Assert.isTrue(fileName != null, "资源名称不能为空!!!");
        return macClient.querySingle(mac, bookId, readModel, fileName);
    }

    /**
     * 获取绘本资源包下载链接
     *
     * @param mac    设备mac地址
     * @param bookId 绘本id
     * @return
     */
    @PostMapping("/query-package")
    public Result queryPackage(String mac, Long bookId, Long channelId) {
        Assert.isTrue(mac != null, "mac不能为空!!!");
        Assert.isTrue(bookId != null, "bookId不能为空!!!");
        Assert.isTrue(channelId != null, "渠道Id不能为空!!!");
        JSONObject obj = macClient.queryPackage(mac, bookId, channelId);
        Integer code = obj.getInteger("code");
        return code.equals(1) ? ResultUtils.returnSuccess(obj) : (code == 0 ? ResultUtils.returnError("Mac地址:" + mac + "不合法!!") : ResultUtils.returnError("bookId:" + bookId + ",资源不存在!!"));
    }

优化方案

我们最终想实现一个:

  1.  验证逻辑和提示信息只需要在一处编写
  2. 如果请求参数的验证规则有变化的话只需要在修改

引入基于注解的验证框架

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>

自定义异常捕获组件,捕获MethodArgumentNotValidException

@ControllerAdvice
public class CustomExceptionHandler {

    /**
     * 请求参数验证异常时返回
     *
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultUtils.Result handler(MethodArgumentNotValidException e) {
        String parameterName = e.getParameter().getParameterName();
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return ResultUtils.returnError(parameterName + message);
    }
}

将请求参数封装成一个dto(这里就用一个简单的类测试下)

@Data
public class User {
    @NotBlank(message = "name参数不能空")
    private String name;
    @Max(100)
    private Integer age;
}

实际接口方法上(@RequestBody是从请求体读取参数,@Validated开启验证,不标注触发不了验证)

   @PostMapping(value = "/args",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
    public ResultUtils.Result getRmoteProviderProt5(@RequestBody @Validated User user) {
        return ResultUtils.returnSuccess(providerFeign.getProt5(user));
    }
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

postman请求测试案例

        name参数不符合验证规则

        age参数不符合验证规则

这样一来,如果我们在多个方法上使用到User作为前端的入参的时候,我们需要修改验证的规则就只需要在User这一个类里面修改了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值