防止表单重复提交机制在JSF2中的兑现

防止表单重复提交机制在JSF2中的实现
防止表单重复提交机制在JSF2中的实现

在B/S系统开发过程中,关于如何防止表单的重复提交问题,也是一个老生常谈的问题,这里说说如何在JSF2的开发环境下防止表单重复提交。

问题解决的思路基本和struts的思路是一致的,那就是

1.生成一个字符串(token),放置在session里,
2.在表单生成时,同时把这个token作为表单的一部分,放置在一个hidden input中,
3.表单提交时,在backingbean中验证一下页面提交过来的token是否和session中的一致。
4.业务完成之后,重置一下token.

因为如果是用浏览器的后退按钮退回到表单页面的话,表单的内容是不会变化的,包括表单里面的token,这样在后退再
提交的时候,由于session中的token已经重置,这时候,我们就认为提交是失败的。

具体实现比较简单,经过2次重构,已经有了比较友好的使用体验。

首先是一个session级的bean, 用它来存储和操作token


/**
 * @author Bill
 * @version 2012-03-21
 */
@SessionScoped
@ManagedBean
public class FormTokenBean {

    public static final String BEAN_NAME = "formTokenBean";

    private String token;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String resetToken() {
        return token = "T" + System.nanoTime();
    }

    public boolean validateToken(String token) {
        return token != null && token.equals(this.token);
    }

    @PostConstruct
    public void init () {
        resetToken();
    }

}


然后需要一个Tag,

/**
 * @author Bill
 * @version 2012-03-27
 */
@FacesComponent("org.billxiong.faces.FormToken")
public class FormTokenTag extends HtmlInputHidden{

    public FormTokenTag() {
        setRendererType("javax.faces.Hidden"); // render as a standard InputHidden
        addValidator(new FormTokenValidator());

        String token = FacesUtils.getObject("formTokenBean.token", String.class); 
        setValue(token);
    }

    @Override
    public void decode(FacesContext context) {
        super.decode(context);

        String clientId = getClientId(context);
        String submittedValue = (String) context.getExternalContext().getRequestParameterMap().get(clientId);

        if(submittedValue != null) {
            setSubmittedValue(submittedValue);
        }
    }

}


在taglib中注册组件,
    <tag>
        <tag-name>formToken</tag-name>
        <component>
            <component-type>org.billxiong.faces.FormToken</component-type>
        </component>
        <attribute>
            <name>id</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>validatorMessage</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
    </tag>


如何验证Token是否有效呢?根据JSF的特点,编写一个Validator,
@FacesValidator("formTokenValidator")
public class FormTokenValidator implements Validator{
    @Override
    public void validate(FacesContext context, UIComponent uiComponent, Object o) throws ValidatorException {
        String token = o == null ? null : o.toString();

        FormTokenBean tokenBean = FacesUtils.getObject(FormTokenBean.BEAN_NAME, FormTokenBean.class);

        if (null == token || null == tokenBean || !tokenBean.validateToken(token)) {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, FacesUtils.getMessage("global.exception.tokenExpired"), 

""));
        }
    }
}


在validator中检查一下是不是和session中一致。

最后,看看页面中的使用,
        <h:form prependId="false">
            <pgfn:formToken/>

            <h:messages errorClass="error-msgs" errorStyle="color: red;"/>

            <h:commandButton id="btnSubmit" action="#{xxxBean.xxxMethod}" value="Submit}"            

        </h:form>


总结:得益于JSF2的大幅改进,使得编写一个标签组件是如此的容易,另外,也要感谢一下struts提供的思路防止表单重复提交机制在JSF2中的兑现

本文系原创,首发Iteye.com, 作者: Bill