在企业系统的开发中,用户表单输入的场景是会经常遇见的,如何让数据校验脱离于业务代码逻辑,谁也不想在逻辑代码里对字段逐一判断。。。。

Spring MVC的校验方式

在使用Spring MVC时的时候,直接使用hibernate-validator的注解,如下:

public class User {
    private Long id;

    @NotBlank(message = "name不能为空")
    @Size(min = 5, max = 10, message = "字符在5到10个")
    private String name;
    private String des;

    @NotNull
    @Max(value = 3, message = "type 参数错误")
    @Min(value = 0, message = "type 参数错误")
    private Integer type;

    @Min(value = 0, message = "参数错误, limit必须大于或等于0")  
    private int limit;

    @Pattern(regexp = "^(true|false)$", message = "参数错误, 参数isActive只能是true或者false")
    private String flag;

    // setters and getters

然后将User对象作为Controller的参数,交给Spring MVC去帮你校验。

通过切面实现自己的注解式数据校验

这是一个SOA的微服务应用,没有controller和Spring MVC,当然也没有所谓的容器(Tomcat、Jetty),对于来自于client的调用,也要进行参数校验。继续基于hibernate-validator,
参看validator的官方文档:
http://hibernate.org/validator/documentation/getting-started/ 引入依赖:

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

<dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.el</artifactId>
   <version>3.0.1-b08</version>
</dependency>

这里需要引入spring boot和aop的一些知识点,自行去网上google吧。我直接上代码了,谁叫我是代码的搬运工。
定义一个切面:

@Aspect  //一个切面
@Configuration  // spring boot 配置类
public class RequestParamValidAspect {
    private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    private final ExecutableValidator methodValidator = factory.getValidator().forExecutables();
    private final Validator beanValidator = factory.getValidator();

    private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object [] params){
        return methodValidator.validateParameters(obj, method, params);
    }

    private <T> Set<ConstraintViolation<T>> validBeanParams(T bean) {
        return beanValidator.validate(bean);
    }

    @Pointcut("execution(* com.jiaobuchong.commodity.service.*.*(..))")
    public void soaServiceBefore(){}

    /* * 通过连接点切入 */
    @Before("soaServiceBefore()")
    public void twiceAsOld1(JoinPoint point) {
        //  获得切入目标对象
        Object target = point.getThis();
        // 获得切入方法参数
        Object [] args = point.getArgs();
        // 获得切入的方法
        Method method = ((MethodSignature)point.getSignature()).getMethod();

        // 校验以基本数据类型 为方法参数的
        Set<ConstraintViolation<Object>> validResult = validMethodParams(target, method, args);

        Iterator<ConstraintViolation<Object>> violationIterator = validResult.iterator();
        while (violationIterator.hasNext()) {
            // 此处可以抛个异常提示用户参数输入格式不正确
            System.out.println("method check---------" + violationIterator.next().getMessage());
        }

        // 校验以java bean对象 为方法参数的 
        for (Object bean : args) {
            if (null != bean) {
                validResult = validBeanParams(bean);
                violationIterator = validResult.iterator();
                while (violationIterator.hasNext()) {
            // 此处可以抛个异常提示用户参数输入格式不正确
                    System.out.println("bean check-------" + violationIterator.next().getMessage());
                }
            }
        }
    }
}

具体的Service

// DemoService.java
public interface DemoService {
    void one(@NotNull(message = "不能为null") Integer a, @NotBlank String b);
    void two(@NotNull(message = "paramsVo不能为null") ParamsVo paramsVo,
             @NotNull(message = "go不能为null") String go);
}

// ParamsVo.java
public class ParamsVo {
    @NotBlank(message = "不能为空")
    private String name;

    @NotBlank
    @Length(min = 2, max = 20, message = "不可以为空,最多20个字")
    private String desc;

    @NotNull
    @Valid  // 需要加上@Valid注解,不然不会校验到Img对象
    private List<Img> imgList;

    @NotNull(message = "length不能为null")
    @Range(min = 3, max = 100, message = "长度范围3-100")
    private Integer length;
    // omitted other code
}

public class Img {
    @NotNull(message = "img id 不能为null")
    private Long id;

    @NotBlank(message = "img name 不能为空")
    private String name;

    // omitted other code
}

运行DemoService:

@Autowired 
    private DemoService demoService;

    @Test
    public void testGo() {
        demoService.one(null, "");

        ParamsVo paramsVo = new ParamsVo();
        List<Img> list = new ArrayList<>();
        Img img = new Img();
        list.add(img);
        paramsVo.setImgList(list);
        paramsVo.setDesc("你");
        paramsVo.setLength(1);
        demoService.two(paramsVo, null);
    }

运行结果:

method check———不能为空 
 method check———不能为null 
 method check———go不能为null 
 bean check——-img name 不能为空 
 bean check——-不能为空 
 bean check——-深度范围3-100 
 bean check——-img id 不能为null 
 bean check——-不可以为空,最多20个字

这样比Spring MVC的校验功能还强大了,

// Spring MVC中对下面这样的校验是没有作用的
void one(@NotNull(message = "不能为null") Integer a, @NotBlank String b);

经过一番改造后,啥都支持了。而且独立于业务逻辑,维护和新增校验都很方便,代码量也变少了!