zoukankan      html  css  js  c++  java
  • Java 简单校验框架

    数据校验框架现状

    在我们的方法入口后面,难免会有如下样子的代码:

            result.setSuccess(false);
            if (StringUtils.isBlank(bizOrder.getThirdOrder())) {
                result.setResultMessage("thirdOrder不能为空");
                return result;
            }
    
            if(bizOrder.getThirdOrder().length() > 100){
                result.setResultMessage("thirdOrder长多过长,必须在100以内");
                return result;
            }
    
            if (StringUtils.isBlank(bizOrder.getSku())) {
                result.setResultMessage("sku不能为空");
                return result;
            }
            if (StringUtils.isBlank(bizOrder.getName())) {
                result.setResultMessage("name不能为空");
                return result;
            }       
    
            if(bizOrder.getName().length() > 20){
                result.setResultMessage("name字数过长");
                return result;
            }
    
            if (bizOrder.getProvince() == 0 || bizOrder.getCity() == 0
                    || bizOrder.getCounty() == 0) {
                result.setResultMessage("地址信息不正确");
                return result;
            }
            if (StringUtils.isBlank(bizOrder.getAddress())) {
                result.setResultMessage("address不能为空");
                return result;
            }

    对于一名有洁癖的程序员,这显然是不行的,我们要更加的优雅。
    好吧,马后炮了,其实早就有这样的规范了:JSR 303 - Bean Validation
    对于其实现,目前用的最广泛的是:Hibernate Validator
    Hiberante Validator, 小巧,规范,易扩展,易整合。
    但是本文不是说它。。。

    对于Web应用,可能更多的我们还是使用Spring MVC的校验,叫做:spring mvc validator
    一百度一大堆,可以跟页面的error标签很好的结合做页面输入的校验。
    但是本文也不是说它。。。

    本文主要是说,来写一个适合自己的校验框架

    数据框架设计目的

    1. 要简单
      只是作为一个小的工具包,代码最多几K,无依赖也是必须的吧
    2. 要优雅
      if.else的调用方式太难看了。看看如下的这种怎么样:
    new Validator().notNull(name, "姓名").notNull(mail, "邮箱");
    • 1
    1. 要易用
      注解是易用的一个好办法,就像JSR303那样
    2. 要可扩展
      要方便客户端程序方便的创建自定义校验器

    总体设计

    这里写图片描述

    首先得起个名字吧,叫MiniValidator
    主要分了两个部分:
    1. 用来给对象进行注解的Annotation及其解析器和校验器
    Annotation ,一组注解
    Parser, 注解解析器,主要处理注解的行为
    AnnotationValidator 使用注解和解析器对传入的对象的字段进行校验
    2. 可扩展的校验器
    AnnotationRule 注解校验rule,作为内置的rule使用
    Rule 用于扩展,可以自定义Rule
    Validator 使用Rule对数据进行校验,或者使用内置的校验器

    实现

    注解校验部分

    首先写一个注解, 例如不能为空白:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NotBlank {
        public String fieldName();
    }

    然后是对应该注解的解析器

    /**
     * 不能为空白校验器
     * @author cdwangzijian
     *
     */
    public class NotBlankParser implements IAnnotationParser {
    
        /**
         * 校验字段f的值不能为null或者是空字符串,校验结果保存在result中
         */
        @Override
        public ValidateResult validate(Field f, Object value) {
            ValidateResult result = new ValidateResult();
            if(f.isAnnotationPresent(NotBlank.class)){
                NotBlank notBlank = f.getAnnotation(NotBlank.class);
                if(value == null || value.toString().length() == 0){
                    result.setMessage(notBlank.fieldName() + "不能为空");
                }
            }
            return result;
        }
    
    }

    下面是使用上面内容的注解校验器

    /**
     * 注解校验器
     * @author cdwangzijian
     *
     */
    public class AnnotationValidator {
        private static final Logger log = Logger.getLogger(AnnotationValidator.class.getName());
    
        private final static List<IAnnotationParser> vList = new ArrayList<IAnnotationParser>();
        static {
            vList.add(new NotNullParser());
            vList.add(new NotBlankParser());
        }
    
        /**
         * 遍历所有字段,用所有解析器进行校验,如果校验失败,则终止校验返回结果,如果校验成功,同样返回校验结果
         * @param t
         * @return
         */
        public static <T> ValidateResult validate(T t){
            ValidateResult result = null;
            for (Field f : t.getClass().getDeclaredFields()) {
                f.setAccessible(true);
                Object value = null;
                try {
                    value = f.get(t);
                } catch (IllegalArgumentException e) {
                    log.log(Level.SEVERE, "Exception", e);
                } catch (IllegalAccessException e) {
                    log.log(Level.SEVERE, "Exception", e);
                }
    
                for (IAnnotationParser va : vList) {
                    result = va.validate(f, value);
                    if(!result.isValid()){
                        return result;
                    }
                }
            }
            return result;
        }
    
        /**
         * 注册解析器
         * @param parser
         */
        public static void register(IAnnotationParser parser){
            vList.add(parser);
        }
    }

    可以看到该校验器已经注册了多个解析器。然后对于传入的对象,会对每一个字段的值进行所有解析器的校验,得到校验结果。

    写一个测试程序吧:

    class User{
        private Long id;
        @NotBlank(fieldName="姓名")
        private String name;
        @Less(fieldName="年龄", value=100)
        private int age;
    
        private String phone;
        private String birthday;
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String getPhone() {
            return phone;
        }
        public void setPhone(String phone) {
            this.phone = phone;
        }
        public String getBirthday() {
            return birthday;
        }
        public void setBirthday(String birthday) {
            this.birthday = birthday;
        }
    }
    
    public class TestAnnotationValidator {
        public static void main(String[] args) {
            User user = new User();
            ValidateResult result = AnnotationValidator.validate(user);
            if(result.isValid()){
                System.out.println("校验通过");
            }else{
                System.out.println(result.getMessage());
            }
        }
    }

    输出的结果:

    姓名不能为空
    • 1

    扩展注解校验器

    基于这个框架,还是可以比较方便的进行扩展的。
    要写一个新的注解,新的解析器,然后注册一下新的解析器就能给新的字段进行校验了。如下:
    新的注解:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DateFormat {
        public String fieldName();
        public String format();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    新的解析器

    /**
     * 日期格式注解解析器
     * 
     * @author cdwangzijian
     *
     */
    public class DateFormatParser implements IAnnotationParser{
    
        /**
         * 校验f字段的值是否符合value的日期格式
         * @see DateFormat
         */
        @Override
        public ValidateResult validate(Field f, Object value) {
            ValidateResult result = new ValidateResult();
            if(f.isAnnotationPresent(DateFormat.class)){
                DateFormat dateFormat = f.getAnnotation(DateFormat.class);
                try {
                    if(value != null){
                        SimpleDateFormat format = new SimpleDateFormat(dateFormat.format());
                        format.parse(value.toString());
                    }
                } catch (ParseException e) {
                    result.setMessage(dateFormat.fieldName() + "不满足格式:" + dateFormat.format());
                }   
            }
            return result;
        }
    }

    使用扩展注解的测试程序:

    public class TestAnnotationValidator {
        public static void main(String[] args) {
            User user = new User();
            user.setName("wzj");
            user.setAge(21);
            user.setBirthday("20150525");
            AnnotationValidator.register(new DateFormatParser());
            ValidateResult result = AnnotationValidator.validate(user);
            if(result.isValid()){
                System.out.println("校验通过");
            }else{
                System.out.println(result.getMessage());
            }
        }
    }

    结果:

    生日不满足格式:yyyy-MM-dd

    好了,注解的部分就这么多了。

    通用校验部分

    通用校验部分首先是一个接口Rule, 供给校验器调用:

    /**
     * 校验规则,用于扩展校验规则
     * @author cdwangzijian
     *
     */
    public interface Rule {
        public String getMessage();
        public boolean isValid();
    }

    使用Rule的校验器:

    /**
     * 通用校验器
     * @author cdwangzijian
     *
     */
    public class Validator {
        public Validator validate(Rule rule) {
            if(this.isValid){
                this.isValid = rule.isValid();
                this.message = rule.getMessage();
            }
            return this;
        }
    
        public Validator validateAnnotation(Object o){
            return validate(new AnnotationRule(o));
        }
    
        public Validator notNull(Object fieldValue, String fieldName) {
            if(this.isValid){
                if(fieldValue == null){
                    this.isValid = false;
                    this.message = fieldName + "不能为空";
                }
            }
            return this;
        }
    
        /**
         * 是否有效
         * @return
         *      true 校验通过,值有效
         *      message 校验未通过的错误信息
         */
        public boolean isValid() {
            return isValid;
        }
        public String getMessage() {
            return message;
        }
    
        private boolean isValid = false;        // 是否有效
        private String message;                 // 错误信息
    }

    该类除了使用Rule以外,还内置了一些notXX的方法,返回this,这样可以用.notXX().notXX().notXX()的结构来进行校验。
    来测试一下:

    public class TestValidator {
        public static void main(String[] args) {
            testMethod("name", null, null, null);
        }
    
        public static void testMethod(String name, String mail, String thirdOrderId, String address){
            Validator v = new Validator().notNull(name, "姓名").notNull(mail, "邮箱").notNull(address, "地址");
            if(v.isValid()){
                System.out.println("校验通过");
            }else{
                System.out.println(v.getMessage());
            }
        }
    }

    结果:

    邮箱不能为空

    扩展通用校验器

    扩展就需要实现Rule接口,如下我们实现一个基于AnnotationValidator的Rule:

    /**
     * 使用AnnotationValidator的校验规则
     * 
     * @see AnnotationValidator
     * @author cdwangzijian
     *
     */
    public class AnnotationRule implements Rule{
        private String message;
        private Object o;
    
        public AnnotationRule(Object o) {
            this.o = o;
        }
        @Override
        public String getMessage() {
            return message;
        }
    
        @Override
        public boolean isValid() {
            ValidateResult result = AnnotationValidator.validate(this.o);
            this.message = result.getMessage();
            return result.isValid();
        }
    
    }

    然后在测试中使用中rule:

    public class TestValidator {
        public static void main(String[] args) {
            new Validator().validate(new AnnotationRule(new User()));
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5

    总结

    到此,这个简单的校验框架就完成了。
    主要的技术上使用了注解,
    然后通过反射再利用注解解析器来进行解析进行校验
    校验器每个方法返回this,可以使用更优雅的代码来完成校验
    并且还可以比较方便的扩展。
    完整的代码,可以从这里获取

    原文地址:https://blog.csdn.net/three_man/article/details/46046779

  • 相关阅读:
    mybatis强化(二)Parameters和Result
    operator new 和 new operator
    hdu 1007 Quoit Design 分治求最近点对
    实现一个简单的shared_ptr
    bzoj 3224: Tyvj 1728 普通平衡树 替罪羊树
    bzoj 2648 SJY摆棋子 kd树
    hdu 2966 In case of failure k-d树
    codeforces 713D D. Animals and Puzzle 二分+二维rmq
    bzoj 1188 : [HNOI2007]分裂游戏 sg函数
    bzoj 1912 : [Apio2010]patrol 巡逻 树的直径
  • 原文地址:https://www.cnblogs.com/brithToSpring/p/14809056.html
Copyright © 2011-2022 走看看