复杂Struts Validation汇总

阅读更多

不得不承认我用了太久的Struts1,但实际上并没有太多好的回忆,在大家都忙着学习Seam、JSF、SpringMVC和Struts2的时候我还不得不使用老掉牙的Struts1。好在每次用还是会用新收获,前提是你跳槽到新公司 -__-!!

    背景:说正题,新公司新项目,主体不在Web App上面,因为系统主要用于数据管理对数据库的完整性和准确性,所以需求上光是验证就有60页的文档,对每一条进入系统的数据都有很严格的要求:包括单个数据和数据之间的关系都有详细的规定,由于我负责的是单个数据的验证,所以本文只关心单个数据的有效性验证。

   

    方案:既然用了Struts,那就很难离开ActionForm(当然也有狠人直接把ActionFrom也干掉@_@),用了ActionForm自然而然的选择Struts Validation。Struts Validation不知道算不算目前Web表现层最好的验证方案,但是肯定是使用最广泛的一种,但是它仍然不够用。

 

    又跑题了,所以对于单个用户录入数据的有效性验证仍然采用Struts Validation,在做过一些改动后,Struts Validation还是能很好的完成这项工作。

 

1.使用validwhen来做简单的交互性验证

     这个网上经常可以搜到,也算是Validation里面最复杂、功能最花哨的一部分,单单为了实现这个功能,Validation就用了4个以上的类去工作,包括字符串的解析这种比较繁琐的工作。它的主要用途是用来做一些简单的交互性的验证,比如A不为空的时候B必须有值,可以做点简单的交互验证哦,但是我并不是很喜欢这东西,写表达式测试起来挺烦的,但是2,3个数据的相互制约还是可以考虑,最常用的就是上面说到的要求,我们可以这样写:


  
  
    test
    ((age == null) or (*this* != null))
  

 

 age不为空的时候name也不能为空,很常见的验证要求。由于这东西每次调试都要重启Server,很烦人,所以我也没做过多的尝试,那个表达式写起来也挺折腾人的,一不小心就失败。如果有兴趣可以参考Validation官方文档中的几条要求做更复杂的表达式来给你的表单做验证。当然这东西还是问题多多的,也可以说凡是用配置文件搞出来的东西,就不会百分之百的符合你的要求。

 

2.使用indexedListProperty为一个循环fields

    这也是Validation里面默认就提供的功能,如果你的表单中包含循环元素生成的元素的时候就可以考虑用indexedListProperty来进行验证,这个很简单,只给出例子:


  
 

indexedListProperty经常与validwhen一起用。

 

3.自定义validator

    Validation里面提供的都是写基本的验证,更多时候需要自定义一些验证来整合我们的业务,所以validation提供了它的扩展接口,其实也就是用配置文件validator-rules.xml的方式来扩展validator。有了这个东西,你可以把任意复杂度的东西放在里面做你想做的验证。这个扩展功能很得人心,这样Validation的灵活度就非常大,更好的做到重用。


    

 

 指定了这个新的validator,你可以任意加入自己的验证机制,在com.cyan.action.validator.CommonValidator中增加validateName()方法,注意在配置文件中已经默认指定了适合Struts ActionForm所需要的参数,你也需要在方法里使用,这样才能整合进Struts Validation并且方便的使用它的Message popup机制。这样你就可以实现你自己的validator:

public static boolean validateName(Object bean, ValidatorAction va,
        Field field, ActionMessages errors, Validator validator,
        HttpServletRequest request) {
        String value = null;
        Integer result = null;

        value = evaluateBean(bean, field);

        // add your validation logic here
        if (!GenericValidator.isBlankOrNull(value)) {
              errors.add(field.getKey(),
                Resources.getActionMessage(validator, request, va, field));            
              return false;
        }

        return true;
}

 这样的扩展写着非常的自如,因为本身Validation对这个CommonValidator的要求很低,它不需要继承和实现任何接口类,当然你也可以继承org.apache.struts.validator.FieldChecks去继承它的一些基本验证。你也同样可以修改validator-rules.xml里面的默认validator指定到你自己的验证规则上。

 

4.JavaScript客户端验证

    这个功能很早就在Struts中得到了支持,因为是客户端就验证了,可以有效减少服务器端的负载,而且Struts Validation JavaScript模式还可以提供Client Server两端验证这种双保险机制,可以减少不少工作量和维护工作。要使用它除了在服务器端进行基础的validation配置以外,还需要给你的页面form增加一点东西(参考:http://www.xker.com/page/e2007/0727/28232.html):

< script language="Javascript1.1" src="js/validator.js">< /script>
< html:javascript formName=”loginForm”>
< html:form action="manageContract.do" onsubmit="return validateLoginForm(this);>
大概是这么写,显然这不是我想说的,网上找这个很容易。我要说的是JavaScript验证方式的问题: JavaScript方式的验证会自动将后台验证代码转化成JavaScript,毕竟是框架生成的,会导致每页JS代码暴增,会影响页面加载速度;还有一个问题就是你自己定义的验证人家没法给你转化成JS,怎么办?用它的JavaScript Plugin。

//TODO

 

5.验证器的继承

     上面已经提到过,系统对验证的要求比较高,common validation就多达100多个,那么按照validation默认的方式对每个ActionForm去写这重复的东西很不现实,所以Validation提供了一个继承方式:

 

这样loginForm就会默认的包含一个name的validator,增加一点配置的重用性。在系统启动的时候,validation会检查所有form的parent并进行合并validator,它会让“子类”form里面的validator覆盖父类的,也就是说你继承了BaseForm,如果有不同的验证,你可以把它覆盖掉。

 

6.扩展Validation

     这里说的扩展Validation不是说写那个validator-rules.xml,我要说的是Validation验证还是太简单,我想更好的使用它就得在它的基础上进行扩展。用框架的时候为了使得框架更适合我们的项目,自然而然的选择去扩展,在Struts1中我们最经常看到的就是扩展org.apache.struts.action.RequestProcessor中的方法,比如其提供的

protected boolean processPreprocess(HttpServletRequest request,
        HttpServletResponse response) {
        return (true);
    }

 这是Struts框架中提供的一个很常用的扩展点,不过自打有了AOP之后它有点落寞,不妨碍我继续数用 :P。

      之所以要扩展,问题就来自于上面的使用validation继承,这个继承的问题在于你的子类form会被强迫加入所有父类的validator,比如父类里面有一个age的验证,而你子类里面也被强制增加了这个age验证器,不管你有没有age这个字段。我不知道是validation没想明白还是我没用明白,这样的继承机制谁还敢用,所以我使用下面的扩展方案,如果有其他办法,希望不吝赐教。

 

在这里先抱怨一下,Validation的确是个好东西,但是它的代码显然是积怨太深,每次验证出问题我去调试跟代码都会很痛苦,因为它写的的确很难懂,好多类、好多方法掺和在一起,也许是我无法理解那些大师们的用意。

 

扩展org.apache.struts.validator.ValidatorForm中的validate()方法,当然你的类是可以继承任意ValidatorForm的子类,我们继承的是LazyValidatorForm。

这个扩展类里面的代码就比较多了(主要因为里面bug比较多),这里只给出部分代码段,主要实现的功能有:

     1.子类实现“伪”继承,就是说子类只根据需要取父类的validator

     2.validator支持多个名字,如:

由于项目中使用的是LazyValidatorForm,form表单里的名字是以map方式传回Action,并支持POJO的populate,所以我们的验证器会有对person.name这种字段的验证,当然扩展的时候也主要在这里出现问题。

 

首先是修改validator加载的来源:

public class BaseForm extends LazyValidatorForm {
        
    public ActionErrors validate(ActionMapping mapping,
        HttpServletRequest request) {
        ServletContext application = getServlet().getServletContext();
        ActionErrors errors = new ActionErrors();

        String validationKey = getValidationKey(mapping, request);

        // Override code Validator validator =
        //     Resources.initValidator(validationKey, this, application, request,  errors, page);
        Validator validator =
            initValidator(validationKey, this, application, request,
                errors, page);

        try {
            validatorResults = validator.validate();
        } catch (ValidatorException e) {
            logger.error(e.getMessage(), e);
        }

        return errors;
    }
}

  顺便你还得提供一个类似于org.apache.struts.validator.Resources.initValidator()的方法:

 

public Validator initValidator(String key, Object bean,
        ServletContext application, HttpServletRequest request,
        ActionMessages errors, int page) {
        ValidatorResources resources =
            Resources.getValidatorResources(application, request);

        Locale locale = RequestUtils.getUserLocale(request, null);
        
        Form form = resources.getForm(locale, key);

        // Override codes here start
        if (form != null) {
            List lFields = (List) getPrivateField(form, "lFields");
            Map hFields = (Map) getPrivateField(form, "hFields");
            
            filterFields(lFields, resources, hFields);
        }
        // end

        Validator validator = new Validator(resources, key);

        validator.setUseContextClassLoader(true);

        validator.setPage(page);

        validator.setParameter(SERVLET_CONTEXT_PARAM, application);
        validator.setParameter(HTTP_SERVLET_REQUEST_PARAM, request);
        validator.setParameter(Validator.LOCALE_PARAM, locale);
        validator.setParameter(ACTION_MESSAGES_PARAM, errors);
        validator.setParameter(Validator.BEAN_PARAM, bean);

        return validator;
    }

 (代码中的private property accessor参考:http://snippets.dzone.com/posts/show/2242

 

因为这并不是框架中提供的一个扩展点,所以这个代码看起来挺别扭,明明只要修改它一句话,但其他的你也得加上。

这样你就可以修改它加载validator的方式,不过这只是个开始,validation并不希望你去这么做,真正的麻烦还在后面。其实麻烦已经出现了,在上面这段覆盖initValidator()的方法中我已经被迫使用了Java reflection中的private property accessor,再一次提醒你人家validation不想你去碰它,god bless me。

    这样总算是获得了Struts Validation里面针对没一个form的validator集合,实际是存放在ValidatorResources中的org.apache.commons.validator.Form中的一个List类型的多个org.apache.commons.validator.Field。然后我们就可以任意的去修改它们,发挥你的聪明才智去适应你的需求吧!记得在修改完毕以后需要完璧归赵:ValidatorResources资源被存放在ServletContext里面作为全局变量保存,你在修改完Form里面的lFields以后需要重新放回去,这个很关键,否则你修改的东西就只能生效一次,下次再来的时候人家就不认识你咯。

    至于如何修改我就不给出代码了,有兴趣的朋友可以看看附件,代码我简单改过,主要是去掉了公司的信息,代码还在测试使用中(因为要适应我们项目的需要),也许你会感到代码写的比较ugly,狂多的for循环和if else,写的时候着实耗费我不少功夫去调试。

 

这样就算是把我目前知道的有关Struts Validation的一些复杂应用总结完了,其实验证就是一个纯体力活,写这些东西就是为了减少奋战在一线的战友们减轻点负担,hope it will help you。

  • BaseForm.rar (3 KB)
  • 下载次数: 35

你可能感兴趣的:(Struts,JavaScript,Apache,Bean,框架)