在B/S系统开发过程中,关于如何防止表单的重复提交问题,也是一个老生常谈的问题,这里说说如何在JSF2的开发环境下防止表单重复提交。
问题解决的思路基本和struts的思路是一致的,那就是
因为如果是用浏览器的后退按钮退回到表单页面的话,表单的内容是不会变化的,包括表单里面的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提供的思路 🙂
本文系原创,作者: Bill