对于webwork的表单校验的改进

 

一、来由

      最近一段时间使用webwork比较多,在使用上有一些想法,比如表单校验,action的使用,webwork的URL格式等等。本次把表单这方面的想法和做法简单总结一下。

      我先把系统结构简单表述一下:

            webwork 2. 2.5 + spring 2.0 + velocity 1.4 + ibatis2.3.4

 

一、webwork的表单校验

      使用webwork的action在编写相应的post处理的时候,可以通过在action的相应方法或者validate方法中进行硬编码校验,或者通过对应的表单校验配置文件 ActionName-validation.xml 或 ActionName-alias-validation.xml等来处理。这样的处理在Action只处理一件事情的时候还比较简单,但是如果Action中包含了n个doXXX(),那么使用起来就比较别扭。

       而且我始终认为ActionName-validation.xml放在Action同一个路径下是一个很差的设计。那是不是可以同时符合两个条件:

       A. Action 可使用多个doXxx()

       B. 针对每个doXxx()进行表单校验

      看了webwork的validation的实现,我们可以新增一下validation替换webwork的validation。

 

三、改进想法

      新增一个Validation拦截器和表单验证文件(可多个),在执行AxtionName.doXxx()方法时,根据对应action.doXxx()方法上的一个注释(比如@Form(group="login"))来查找对应的表单验证配置,然后执行校验。

对于表单的详细校验方法还是webwork本身的校验方法,只是把查找表单和表单配置文件的格式调整一下。

     那么需要做下面几件事情:

      A. 定义表单配置文件格式。

      B. 编写Validation拦截器:ValidationInterceptor

      C. 编写表单校验文件加载处理,FormResolver 及实现 DefaultFormResolver

      D. 编写annotation类:Form 和 表单描述类 Group

四、定义表单文件

      文件格式用dtd表述如下:

 

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT forms (group)+>
<!ELEMENT group (field | validator)+>
<!ATTLIST group
	name CDATA #REQUIRED
>
<!ELEMENT field (field-validator+)>
<!ATTLIST field
	name CDATA #REQUIRED
>
<!ELEMENT field-validator (param*, message)>
<!ATTLIST field-validator
	type CDATA #REQUIRED
	short-circuit (true | false) "false"
>
<!ELEMENT validator (param*, message)>
<!ATTLIST validator
	type CDATA #REQUIRED
	short-circuit (true | false) "false"
>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param
	name CDATA #REQUIRED
>
<!ELEMENT message (#PCDATA)>
<!ATTLIST message
	key CDATA #IMPLIED
>

    用树形表述如下:

 

    forms

      |

      |----group(*)

               |

               |-----field

               |         |

               |         |-- field-validator

               |-----validator


    其实就是把webwork的actionName-validation.xml文件中的配置放入到group中,并给group设置一个属性name,在外层嵌套一个forms。

    那么就可以把所有的表单验证文件放入到一个配置文件,或者多个配置文件中。例子如下:

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form PUBLIC "-//alisoft software//DTD eshop webwork Validator 1.0//EN" "http://www.alisoft.com/dtd/eshop-validator-1.0.dtd">
<forms>
	<group name="login">
		<field name="user.loginId">
			<field-validator type="email">
				<message>必须为合法的Email格式</message>
			</field-validator>
		</field>
		<field name="user.password">
			<field-validator type="requiredstring">
				<message>密码不能为空</message>
			</field-validator>
			<field-validator type="stringlength">
				<param name="minLength">4</param>
				<param name="maxLength">12</param>
				<message>长度必须在${minLength}和${maxLength}之间,当前的长度为${user.password.length()}
				</message>
			</field-validator>
		</field>
	</group>
</forms>

 

五、编写Validation拦截器:ValidationInterceptor

     代码如下:

 

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.MethodFilterInterceptor;
import com.xbuy.eshop.framework.util.BeanFactoryUtil;
import com.xbuy.eshop.framework.validator.form.Form;
import com.xbuy.eshop.framework.validator.form.FormResolver;
import com.xbuy.eshop.framework.validator.form.Group;

import freemarker.template.utility.ClassUtil;

@SuppressWarnings("serial")
public class ValidationInterceptor extends MethodFilterInterceptor {

    protected void doBeforeInvocation(ActionInvocation invocation) throws Exception {

        Object action = invocation.getAction();
        String method = invocation.getProxy().getMethod();
        Form form = getForm(invocation, method);

        //有设置注释的进行validate
        if (form != null) {
            String groupName = form.group();

             FormResolver formResolver = (FormResolver) BeanFactoryUtil
                    .getBean(FormResolver.BEAN_NAME);            
             if (formResolver == null) {
                log.error("Validating error:not found fromResolver!");
                return;
            }

            Group group = formResolver.fetchGroup(groupName);
            if (group == null) {
                log.error("Validating error:not found name='" + groupName + "'s group !");
                return;
            }

            group.validate(action);
        }

        if (log.isDebugEnabled()) {
            log.debug("Validating " + invocation.getProxy().getNamespace() + "/"
                    + invocation.getProxy().getActionName() + " with method "
                    + invocation.getProxy().getMethod() + ".");
        }

    }

    @SuppressWarnings("unchecked")
    private Form getForm(ActionInvocation invocation, String method) throws ClassNotFoundException {
        Class cls = ClassUtil.forName(invocation.getProxy().getConfig().getClassName());
        Method[] methods = cls.getMethods();
        if (methods != null) {
            Method currentMethod = null;
            for (Method ms : methods) {
                if (method.equalsIgnoreCase(ms.getName())) {
                    currentMethod = ms;
                    break;
                }
            }

            if (currentMethod != null) {
                Annotation[] anns = currentMethod.getAnnotations();
                if (anns != null) {
                    Annotation currentAnn = null;
                    for (Annotation ann : anns) {
                        if (ann.annotationType().equals(Form.class)) {
                            currentAnn = ann;
                            break;
                        }
                    }
                    return (Form) currentAnn;

                }
            }
        }
        return null;
    }

    protected String doIntercept(ActionInvocation invocation) throws Exception {

        doBeforeInvocation(invocation);

        return invocation.invoke();
    }
}

   其中:

FormResolver formResolver = (FormResolver) BeanFactoryUtil.getBean(FormResolver.BEAN_NAME);   

   表示从Spring BeanFactory中获取对应的Bean:    formResolver

 

六、编写表单校验文件加载处理,FormResolver 及实现 DefaultFormResolver      

     FormResolver如下:

 

public interface FormResolver {
    public static final String BEAN_NAME = "formResolver";

    /**
     * 获取group定义
     * 
     * @param groupName
     * @return
     */
    Group fetchGroup(String groupName);
    
}

    DefaultFormResolver如下:

 

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.opensymphony.util.FileManager;
import com.opensymphony.xwork.util.DomHelper;
import com.opensymphony.xwork.util.TextParseUtil;
import com.opensymphony.xwork.validator.Validator;
import com.opensymphony.xwork.validator.ValidatorConfig;
import com.opensymphony.xwork.validator.ValidatorFactory;

/**
 * 表单配置文件加载
 * 
 * @author qianjun.liqj
 */
public class DefaultFormResolver implements FormResolver, Reloadable,InitializingBean {

    private static final Log    LOG                       = LogFactory
                                                                  .getLog(DefaultFormResolver.class);

    private static final String MULTI_TEXTVALUE_SEPARATOR = " ";

    /**
     * 表单配置文件,格式为: form/form.xml,form/form1.xml
     */
    private String              configLocation            = "form/form.xml";

    /**
     * 表单配置,格式:groupName - group
     */
    private Map<String, Group>  groups                    = new HashMap<String, Group>();

    public String getConfigLocation() {
        return configLocation;
    }

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }

    /*
     * (non-Javadoc)
     * @see
     * com.xbuy.eshop.framework.validator.form.FormResolver#fetchGroup(java.
     * lang.String)
     */
    public Group fetchGroup(String groupName) {

        if (groupName == null || groupName.trim().length() == 0) {
            return null;
        }

        return this.groups.get(groupName);
    }

    /*
     * (non-Javadoc)
     * @see
     * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        if (configLocation == null || configLocation.trim().length() == 0) {
            return;
        }
        this.reload();
    }

    /**
     * Parse resource for a list of ValidatorConfig objects.
     * 
     * @param is input stream to the resource
     * @param resourceName file name of the resource
     * @return List list of ValidatorConfig
     */
    private void parseActionValidatorConfigs(InputStream is, final String resourceName) {

        InputSource in = new InputSource(is);
        in.setSystemId(resourceName);

        //设置DTD
        Map<String, String> dtdMappings = new HashMap<String, String>();
        dtdMappings.put("-//alisoft software//DTD eshop webwork Validator 1.0//EN",
                "com/xbuy/eshop/framework/validator/eshop-validator-1.0.dtd");

        Document doc = DomHelper.parse(in, dtdMappings);

        if (doc == null) {
            return;
        }

        //处理group
        NodeList groupNodes = doc.getElementsByTagName("group");
        for (int j = 0; j < groupNodes.getLength(); j++) {

            Element groupElement = (Element) groupNodes.item(j);
            String groupName = groupElement.getAttribute("name");
            if (groupName == null || groupName.trim().length() == 0) {
                continue;
            }

            Group group = new Group();
            group.setName(groupName);

            //处理field
            NodeList fieldNodes = groupElement.getElementsByTagName("field");
            for (int i = 0; i < fieldNodes.getLength(); i++) {

                List<ValidatorConfig> cfgs = new ArrayList<ValidatorConfig>();

                Element fieldElement = (Element) fieldNodes.item(i);
                String fieldName = fieldElement.getAttribute("name");
                Map<String, String> extraParams = new HashMap<String, String>();
                extraParams.put("fieldName", fieldName);

                //处理 field-validator 列表
                NodeList validatorNodes = fieldElement.getElementsByTagName("field-validator");
                addValidatorConfigs(validatorNodes, extraParams, cfgs);

                //转化为 validator 列表
                List<Validator> validators = new ArrayList<Validator>(cfgs.size());
                for (Iterator<ValidatorConfig> iterator = cfgs.iterator(); iterator.hasNext();) {
                    ValidatorConfig cfg = iterator.next();
                    Validator validator = ValidatorFactory.getValidator(cfg);
                    validator.setValidatorType(cfg.getType());
                    validators.add(validator);
                }
                group.setValidators(fieldName, validators);
            }

            groups.put(groupName, group);
        }
    }

    /**
     * Extract trimmed text value from the given DOM element, ignoring XML
     * comments. Appends all CharacterData nodes and EntityReference nodes into
     * a single String value, excluding Comment nodes. This method is based on a
     * method originally found in DomUtils class of Springframework.
     * 
     * @see org.w3c.dom.CharacterData
     * @see org.w3c.dom.EntityReference
     * @see org.w3c.dom.Comment
     */
    private static String getTextValue(Element valueEle) {
        StringBuffer value = new StringBuffer();
        NodeList nl = valueEle.getChildNodes();
        boolean firstCDataFound = false;
        for (int i = 0; i < nl.getLength(); i++) {
            Node item = nl.item(i);
            if ((item instanceof CharacterData && !(item instanceof Comment))
                    || item instanceof EntityReference) {
                final String nodeValue = item.getNodeValue();
                if (nodeValue != null) {
                    if (firstCDataFound) {
                        value.append(MULTI_TEXTVALUE_SEPARATOR);
                    } else {
                        firstCDataFound = true;
                    }
                    value.append(nodeValue.trim());
                }
            }
        }
        return value.toString().trim();
    }

    /**
     * 解析field节点下的所有field-validator子节点
     * 
     * @param validatorNodes field-validator节点列表
     * @param extraParams 额外参数
     * @param validatorCfgs ValidatorConfig结果列表
     */
    private void addValidatorConfigs(NodeList validatorNodes, Map<String, String> extraParams,
                                     List<ValidatorConfig> validatorCfgs) {

        for (int j = 0; j < validatorNodes.getLength(); j++) {

            Element validatorElement = (Element) validatorNodes.item(j);
            String validatorType = validatorElement.getAttribute("type");

            //获取param 
            Map<String, String> params = new HashMap<String, String>(extraParams);
            NodeList paramNodes = validatorElement.getElementsByTagName("param");
            for (int k = 0; k < paramNodes.getLength(); k++) {
                Element paramElement = (Element) paramNodes.item(k);
                String paramName = paramElement.getAttribute("name");
                params.put(paramName, getTextValue(paramElement));
            }

            // ensure that the type is valid...
            ValidatorFactory.lookupRegisteredValidatorType(validatorType);

            ValidatorConfig vCfg = new ValidatorConfig(validatorType, params);
            vCfg.setLocation(DomHelper.getLocationObject(validatorElement));

            vCfg.setShortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit"))
                    .booleanValue());

            NodeList messageNodes = validatorElement.getElementsByTagName("message");
            Element messageElement = (Element) messageNodes.item(0);
            String key = messageElement.getAttribute("key");

            if ((key != null) && (key.trim().length() > 0)) {
                vCfg.setMessageKey(key);
            }

            final Node defaultMessageNode = messageElement.getFirstChild();
            String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode
                    .getNodeValue();
            vCfg.setDefaultMessage(defaultMessage);
            validatorCfgs.add(vCfg);
        }
    }

    @SuppressWarnings("unchecked")
    public void reload() {

        if (configLocation == null || configLocation.trim().length() == 0) {
            return;
        }
        
        this.groups.clear();
        
        Set<String> configFiles = TextParseUtil.commaDelimitedStringToSet(configLocation);
        for (String fileName : configFiles) {
            InputStream is = null;
            try {
                is = FileManager.loadFile(fileName, this.getClass());

                if (is != null) {
                    parseActionValidatorConfigs(is, fileName);
                }
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        LOG.error("Unable to close input stream for " + fileName, e);
                    }
                }
            }
        }

    }


}

   编写annotation类:Form 和 表单描述类 Group   

 

import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

/**
 * Marks a field or method param to read parameters from request.
 * 
 * @author qianjun.liqj
 */
@Target( { ElementType.METHOD })
@Documented
@Retention(value = RUNTIME)
public @interface Form {

    /**
     * 设置表单校验对应的groupName.
     * 
     * @return
     */
    String group() default "";

}

 

/**
 * 表单验证组
 * 
 * @author qianjun.liqj
 */
public class Group {

    private static final Log             LOG        = LogFactory.getLog(Group.class);
    private String                       name;

    private List<String>                 fields     = new ArrayList<String>();
    private Map<String, List<Validator>> validators = new HashMap<String, List<Validator>>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getFields() {
        return fields;
    }

    public void addField(String field) {
        this.fields.add(field);
    }

    public void setFields(List<String> fields) {
        this.fields = fields;
    }

    public Map<String, List<Validator>> getValidators() {
        return validators;
    }

    public void setValidators(String fieldName, List<Validator> validators) {
        this.validators.put(fieldName, validators);
    }

    public List<Validator> getValidators(String fieldName) {
        return this.validators.get(fieldName);
    }

    //TODO 
    public List<Validator> getAllValidators() {
        List<Validator> validators = new ArrayList<Validator>();
        for (Iterator<String> it = this.validators.keySet().iterator(); it.hasNext();) {
            validators.addAll(this.validators.get(it.next()));
        }
        return validators;
    }

    /**
     * Validates the given object using action .
     * 
     * @param object the action to validate.
     * @throws ValidationException if an error happens when validating the
     *             action.
     */
    public void validate(Object object) throws ValidationException {
        ValidatorContext validatorContext = new DelegatingValidatorContext(object);
        validate(object, validatorContext);
    }

    /**
     * Validates an action give a validation context.
     * 
     * @param object the action to validate.
     * @param validatorContext
     * @throws ValidationException if an error happens when validating the
     *             action.
     */
    public void validate(Object object, ValidatorContext validatorContext)
            throws ValidationException {
        List<Validator> validators = getAllValidators();
        if (validators == null)
            return;

        Set<String> shortcircuitedFields = null;

        for (Iterator<Validator> iterator = validators.iterator(); iterator.hasNext();) {
            final Validator validator = iterator.next();
            try {
                validator.setValidatorContext(validatorContext);

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Running validator: " + validator + " for object " + object);
                }

                FieldValidator fValidator = null;
                String fullFieldName = null;

                if (validator instanceof FieldValidator) {
                    fValidator = (FieldValidator) validator;
                    fullFieldName = fValidator.getValidatorContext().getFullFieldName(
                            fValidator.getFieldName());

                    if ((shortcircuitedFields != null)
                            && shortcircuitedFields.contains(fullFieldName)) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Short-circuited, skipping");
                        }

                        continue;
                    }
                }

                if (validator instanceof ShortCircuitableValidator
                        && ((ShortCircuitableValidator) validator).isShortCircuit()) {
                    // get number of existing errors
                    List errs = null;

                    if (fValidator != null) {
                        if (validatorContext.hasFieldErrors()) {
                            Collection fieldErrors = (Collection) validatorContext.getFieldErrors()
                                    .get(fullFieldName);

                            if (fieldErrors != null) {
                                errs = new ArrayList(fieldErrors);
                            }
                        }
                    } else if (validatorContext.hasActionErrors()) {
                        Collection actionErrors = validatorContext.getActionErrors();

                        if (actionErrors != null) {
                            errs = new ArrayList(actionErrors);
                        }
                    }

                    validator.validate(object);

                    if (fValidator != null) {
                        if (validatorContext.hasFieldErrors()) {
                            Collection errCol = (Collection) validatorContext.getFieldErrors().get(
                                    fullFieldName);

                            if ((errCol != null) && !errCol.equals(errs)) {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Short-circuiting on field validation");
                                }

                                if (shortcircuitedFields == null) {
                                    shortcircuitedFields = new TreeSet();
                                }

                                shortcircuitedFields.add(fullFieldName);
                            }
                        }
                    } else if (validatorContext.hasActionErrors()) {
                        Collection errCol = validatorContext.getActionErrors();

                        if ((errCol != null) && !errCol.equals(errs)) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Short-circuiting");
                            }

                            break;
                        }
                    }

                    continue;
                } else {//
                    validator.validate(object);
                }

            } finally {
                validator.setValidatorContext(null);
            }
        }

    }

}

   ok,到此所有的代码编写完毕。

 

七、使用方法

     在Action.doXxxx()方法增加标注,编写配置文件即可。如下:

 

@Form(group="login")
    @Override
    public final String execute(){}

   配置文件见上述例子

 

八、进一步的想法

      鉴于spring+webwork/struts2.0 +velocity的结构,对于表单的前端校验需要自己来实现,而且不能和webwork的表单校验框架联系起来,这是一个遗憾。

      我觉得可以通过JS来实现一个表单的前端校验框架,详细的校验方法由webwork的表单校验器来生成,在渲染页面的时候把对应的JS Function 渲染到页面中。当submit表单的时候,该Js框架拦截Form.submit()事件执行表单校验,该部分可参考valang的实现机制。

       另外,在表单的csrf攻击上也可通过验证器统一处理。

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值