基于Spring Validation自定义校验注解

Posted 抓手

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Spring Validation自定义校验注解相关的知识,希望对你有一定的参考价值。

常用的原生校验注解有:

@NotNull 所有对象判空
@NotBlank 字符串判空
@NotEmpty 集合判空

自定义校验注解实现方式:

引入依赖

如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.1.Final</version>
</dependency>

定义校验注解

自定义一个校验注解,针对字符串参数校验,校验逻辑是字符串必须是"xzh"。

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

/**
 * @author 向振华
 * @date 2022/11/23 16:52
 */
@Documented
@Constraint(validatedBy = EqualsXzhValidator.class)
@Target( METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE )
@Retention(RUNTIME)
public @interface EqualsXzh 

    String message() default "必须是xzh";

    Class<?>[] groups() default  ;

    Class<? extends Payload>[] payload() default  ;

实现校验逻辑

校验逻辑是字符串必须是"xzh"。

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @author 向振华
 * @date 2022/11/23 16:56
 */
public class EqualsXzhValidator implements ConstraintValidator<EqualsXzh, String> 

    public EqualsXzhValidator() 
    

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) 
        // 自定义校验规则,字段的value必须是"xzh",否则抛异常
        return "xzh".equals(value);
    

全局异常捕获处理

参数校验会默认抛出MethodArgumentNotValidException异常,通常使用全局异常捕获拦截,然后返回提示信息出去 。

import com.xzh.exception.BusinessException;
import com.xzh.web.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.StringJoiner;

/**
 * 全局异常捕获处理
 *
 * @author 向振华
 * @date 2022/11/23 16:52
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler 

    /**
     * 异常处理程序
     *
     * @param request
     * @param exception
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public Object handler(HttpServletRequest request, Exception exception) 
        String errorLog = this.errorLog(request, exception);
        log.error("全局异常捕获日志 ---> ", errorLog);

        String message;
        if (exception instanceof MethodArgumentNotValidException) 
            MethodArgumentNotValidException e = (MethodArgumentNotValidException) exception;
            BindingResult bindingResult = e.getBindingResult();
            StringJoiner sj = new StringJoiner(",");
            for (FieldError fieldError : bindingResult.getFieldErrors()) 
                String longField = fieldError.getField();
                String shortField = longField.substring(longField.lastIndexOf(".") + 1);
                String defaultMessage = fieldError.getDefaultMessage();
                sj.add(shortField + defaultMessage);
            
            message = sj.toString();
         else if (exception instanceof BusinessException) 
            BusinessException e = (BusinessException) exception;
            message = e.getMessage();
         else 
            message = "系统异常,请稍后重试";
        

        return ApiResponse.fail(message);
    


    private String errorLog(HttpServletRequest request, Exception exception) 
        StringWriter sw = new StringWriter();
        sw.append(String.format("[接口] %s\\n", request.getRequestURL()));
        exception.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    

参数校验工具

 参数校验也可以使用工具类进行校验,校验不通过抛出业务异常(也会被全局异常捕获拦截)

import com.xzh.exception.BusinessException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
import java.util.StringJoiner;

/**
 * 参数校验工具
 *
 * @author 向振华
 * @date 2022/11/23 16:52
 */
public class ValidatedUtils 

    private static final Validator VALIDATOR;

    static 
        VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
    

    private ValidatedUtils() 
    

    public static void verify(Object object, Class<?>... groups) 
        Set<ConstraintViolation<Object>> constraintViolations = VALIDATOR.validate(object, groups);
        if (!constraintViolations.isEmpty()) 
            StringJoiner sj = new StringJoiner(",");
            for (ConstraintViolation<Object> constraint : constraintViolations) 
                sj.add(constraint.getPropertyPath().toString() + constraint.getMessage());
            
            throw new BusinessException(sj.toString());
        
    

使用

在参数上面加上@EqualsXzh注解即可

@EqualsXzh
private String name;

如果是嵌套校验还需要加上@Valid

@Data
public class OrderDTO 

    @EqualsXzh
    private String name;

    @Valid
    private AccountDTO account;

    @Valid
    private List<OrderItemDTO> orderItemList;

在对象前加@Validated注解

@PostMapping("/test")
public ApiResponse<String> test(@RequestBody @Validated OrderDTO dto) 
    // TODO
    return ApiResponse.success();

校验不通过会出现提示:name必须是xzh

其中BusinessException是自定义的业务异常,ApiResponse是自定义的通用返回格式。

补充

以前的校验注解文章

Spring Boot 全局异常处理 与 Hibernate Validator校验框架整合_抓手的博客-CSDN博客

Spring校验框架的使用 @Validated_抓手的博客-CSDN博客

 

以上是关于基于Spring Validation自定义校验注解的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot Validation参数校验 详解自定义注解规则和分组校验

spring validation的使用与自定义校验注解

Spring validation中自定义的校验错误提示信息如何支持国际化

自定义validation注解:解决动态多字段联动校验问题

Spring Validation最佳实践及其实现原理,参数校验没那么简单!

Spring MVC学习—Validation基于注解的声明式数据校验机制全解一万字