2023年11月29日发(作者:)

springboot⾃定义注解验证参数类型并提⽰准确异常

最近做项⽬任务时, 因前端请求接⼝时未进⾏参数验证, 因此⼀些数值类型参数传递数据被录⼊了String类型, 服务端提⽰"参数解析失败", 测试提了个提⽰信息

不准确的bug, 虽然前端加⼀下验证, 后端接⼝也拦截掉了异常, 但既然提出最好可以处理⼀下, 当然要看看能不能解决此问题, 将最符合的提⽰返回给前端.

⾸先, 如果需要最准确的提⽰信息, 那么就需要⽤到⾃定义注解了, 在⾃定义注解中填⼊相应的提⽰信息

/**

*

数据绑定异常提⽰语

*/

@Target({ ElementType.FIELD, ElementType.PARAMETER })

@Retention(RetentionPolicy.RUNTIME)

@Inherited

public @interface DataBindErrorTips {

String value();

}

使⽤⽅法: 直接在请求实体类的字段上添加注解并给予⾃定义提⽰信息

/**

*

商品编号

*/

@DataBindErrorTips("商品编号格式不正确")

private Integer spuCode;

现在基本的内容已经准备好, 剩下的就是处理异常了, ⾸先既然是绑定的注解⾸先想到BindException异常处理,如下编写

@ExceptionHandler(BindException.class)

@ResponseStatus(value = HttpStatus.OK)

public Result validExceptionHandler(BindException e, HttpServletRequest request) {

BindingResult bindingResult = e.getBindingResult();

FieldError fieldError = bindingResult.getFieldErrors().stream().min(Comparator.comparing(FieldError::getField)).orElse(null);

if (fieldError == null){

return Response.build(ILLEGALA_RGUMENT_CODE, "参数格式不正确");

}

String fieldName = fieldError.getField();

Class targetCalss = Objects.requireNonNull(bindingResult.getTarget()).getClass();

String erroMessage = getDataBindErrorTips(targetCalss, fieldName);

if (StringUtils.isEmpty(erroMessage)) {

erroMessage = fieldError.getDefaultMessage();

if (erroMessage != null && erroMessage.startsWith("Failed to convert property")) {

erroMessage = "参数格式不正确";

}

}

String rejectedValue = Objects.toString(fieldError.getRejectedValue());

if (rejectedValue.length() > 30) {

rejectedValue = rejectedValue.substring(0, 30) + "...";

}

log.error("BindException URI={}, field={}, rejectedValue={}, errorMessage={}", request.getRequestURI(), fieldName, rejectedValue, erroMessage);

return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);

}

//

获取字段所在实体类中所对应的⾃定义提⽰信息

private String getDataBindErrorTips(Class targetCalss, String fieldName) {

if (targetCalss == null || StringUtils.isEmpty(fieldName)) {

return null;

}

while (targetCalss != Object.class) {

Field field;

try {

field = targetCalss.getDeclaredField(fieldName);

DataBindErrorTips dataBindErrorTips = field.getDeclaredAnnotation(DataBindErrorTips.class);

return dataBindErrorTips == null ? null : dataBindErrorTips.value();

} catch (NoSuchFieldException e) {

targetCalss = targetCalss.getSuperclass();

}

}

return null;

}

测试了⼀下, 发现get请求可以准确提⽰,⽽post请求未进⼊该异常拦截中, 查看⼯作台异常信息

ssageNotReadableException: Invalid JSON input: Cannot deserialize value of

type from String “abc”: not a valid Integer value; nested exception is

r

dFormatException: Cannot deserialize value of type from String

r

“abc”: not a valid Integer value

从中可以看到报错异常是InvalidFormatException, 那么好了, 直接在此异常中处理即可

@ResponseStatus(HttpStatus.OK)

@ExceptionHandler(InvalidFormatException.class)

public Result handInvalidFormatException(InvalidFormatException e){

String erroMessage = "";

List<JsonMappingException.Reference> path = e.getPath();

Reference reference;

if (!CollectionUtils.isEmpty(path) && (reference = path.get(0)) != null) {

erroMessage = getDataBindErrorTips(reference.getFrom().getClass(), reference.getFieldName());

if (erroMessage == null) {

erroMessage = "参数名:"+reference.getFieldName()+" 输⼊不合法,需要的是 "+invalidFormatException.getTargetType().getName() + " 类型,"+"提交

的值是:"+invalidFormatException.getValue().toString();

}

}else{

erroMessage = "参数解析失败";

}

return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);

}

测试运⾏, 没有被异常拦截到, 不应该啊, 到底哪⾥出现问题了, 回到上⾯再仔细看看报错异常信息原来在InvalidFormatException前⾯还有

⼀个上层异常HttpMessageNotReadableException那好办了, 直接将上述代码中的改为

, 这样不就可以拦截到了吗?测试后发现果然可以! 虽然可以拦截并且提⽰信息正确但拦截的异

常类与处理的异常类不统⼀,为了以后的扩展性, 最好还是拦截异常与参数统⼀都⽤HttpMessageNotReadableException,那么就需要将

HttpMessageNotReadableException 转化为我们需要的InvalidFormatException代码如下

@ResponseStatus(HttpStatus.OK)

@ExceptionHandler(HttpMessageNotReadableException.class)

public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {

String erroMessage = null;

Throwable rootCause = e.getRootCause();

if (rootCause instanceof JsonMappingException) {

erroMessage = getDataBindErrorTips(((JsonMappingException) rootCause).getPath());

}

if (erroMessage == null) {

erroMessage = "参数解析失败";

}

return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);

}

private String getDataBindErrorTips(List<Reference> path) {

if (CollectionUtils.isEmpty(path)) {

return null;

}

for (int i = path.size() - 1; i >= 0; i--) {//

基本只需要最后⼀条数据

Reference reference = path.get(i);

if (reference == null) {

continue;

}

Object target = reference.getFrom();

if (target == null) {

continue;

}

Class targetCalss = target.getClass();

String result = this.getDataBindErrorTips(target.getClass(), reference.getFieldName());

if (!StringUtils.isEmpty(result)) {

return result;

}

}

return null;

}

现在⽆论是gat请求或者post请求, 都已经能返回准确的提⽰信息了