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. 要易用
    注解是易用的一个好办法,就像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());
        }
    }
}

输出的结果:

姓名不能为空

扩展注解校验器

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

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

新的解析器

/**
 * 日期格式注解解析器
 *
 * @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()));
    }
}

总结

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

时间: 2024-09-20 21:31:39

Java 简单校验框架的相关文章

在Java的Hibernate框架中使用SQL语句的简单介绍_java

Hibernate中有HQL查询语法.但我们用得比较熟的还是数SQL语句,那么应该怎么来让Hibernate支持SQL呢?这个不用我们去考虑了,Hibernate团队已经早就做好了.        废话不说,直接来例子啦. select * from t_user usr     上面是一条SQL语句,又是废话,是个人都知道.我们想让Hibernate执行这条语句,怎么办呢?看代码: Query query = session.createSQLQuery("select * from t_us

Java持久层框架MyBatis简单实例_java

什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .iBATIS一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架.iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO). MyBatis 本是apache的一个开源项目iB

基于AJAX技术实现Struts校验框架

ajax 提要 实时的数据校验是AJAX技术的重要优点之一,Struts校验框架通过加入这种技术进一步丰富了其MVC,从而使得Web应用程序的开发效果更接近于桌面应用程序. 一. 引言 校验框架的根本目的是实现域校验.在Web应用程序中有许多方法可以实现域校验,总体上可以分为两类:服务器端和客户端.其中,Struts校验框架是适合于基于Java的Web应用程序环境最好的框架之一.它可以使用服务器端校验来配置应用程序并且使用在校验过程(在请求处理期间激活)中生成的错误消息:而且它也可以通过在请求页

让JAVA 和 .NET框架共存

.net框架 原创作者:Ashish Banerjee 翻译整理:51DOTNET CLUB(WWW.51DOTNET.COM)SLASH 目的:对JAVA与.NET框架共存的可能性做一个评估 目标受众:JAVA程序员和系统工程师    提要:        首先是对JAVA 和 .NET平台的构成做一个分析,然后是我个人对JAVA如何形成的一个认识,接着是分析微软和SUN之间的合作与分歧,最后是JAVA与.NET合作的前景.      我个人强烈认为JAVA与.NET将在不久的未来逐步的统一起

是否能让JAVA 和 .NET框架共存(转)

.net框架 原创作者:Ashish Banerjee 翻译整理:51DOTNET CLUB(WWW.51DOTNET.COM)SLASH 目的:对JAVA与.NET框架共存的可能性做一个评估 目标受众:JAVA程序员和系统工程师    提要:        首先是对JAVA 和 .NET平台的构成做一个分析,然后是我个人对JAVA如何形成的一个认识,接着是分析微软和SUN之间的合作与分歧,最后是JAVA与.NET合作的前景.      我个人强烈认为JAVA与.NET将在不久的未来逐步的统一起

hibernate-求助!!!!java编程中框架的使用流程

问题描述 求助!!!!java编程中框架的使用流程 开始接触框架,对框架的概念还不是很理解,我想问问,框架是什么?在编程中使用一个框架的大致流程,比如hibernate如果我想使用hibernate框架,是要先做环境搭建吗? 框架使用的流程都是相似的吗? 解决方案 框架和类库都是别人事先编写好的程序,供你重用的.类库重用的是功能,框架重用的是流程(当然框架一般也附带类库) 用hibernate很简单,导入对应的jar包,根据文档或者例子,配置好配置文件(一般是jdbc连接),在程序中就能使用了.

Java的MyBatis框架中对数据库进行动态SQL查询的教程_java

其实MyBatis具有的一个强大的特性之一通常是它的动态 SQL 能力. 如果你有使用 JDBC 或其他 相似框架的经验,你就明白要动态的串联 SQL 字符串在一起是十分纠结的,确保不能忘了空格或在列表的最后省略逗号.Mybatis中的动态 SQL 可以彻底处理这种痛苦.对于动态SQL,最通俗简单的方法就是我们自己在硬编码的时候赋予各种动态行为的判断,而在Mybatis中,用一种强大的动态 SQL 语 言来改进这种情形,这种语言可以被用在任意映射的 SQL 语句中.动态 SQL 元素和使用 JS

java持久层框架mybatis防止sql注入的方法_java

sql注入大家都不陌生,是一种常见的攻击方式,攻击者在界面的表单信息或url上输入一些奇怪的sql片段,例如"or '1'='1'"这样的语句,有可能入侵参数校验不足的应用程序.所以在我们的应用中需要做一些工作,来防备这样的攻击方式.在一些安全性很高的应用中,比如银行软件,经常使用将sql语句全部替换为存储过程这样的方式,来防止sql注入,这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式. mybatis框架作为一款半自动化的持久层框架,其sql语句都要我们自己来手

Java的Struts框架简介与环境配置教程_java

Struts2是流行和成熟的基于MVC设计模式的Web应用程序框架. Struts2不只是Struts1下一个版本,它是一个完全重写的Struts架构. WebWork框架开始以Struts框架为基础,其目标是提供一个加强和改进框架Struts来使web开发的开发人员更容易. 一段时间后,WebWork框架和Struts社区联手打造的著名的Struts2框架. Struts 2框架的特点: 这里有一些强大的功能,可能会迫使你考虑Struts2: POJO表单和POJO动作 - Struts2的S