struts2 使用消息存储拦截器进行验证
struts2 validation using message store interceptor
考虑一下:
- 用户点击了 link
- 请求转到 DisplayLoginAction
- 显示Login.jsp
- 用户输入他的凭据
- 表单映射到 ValidateLoginAction
- ValidateLoginAction 中的验证失败
- ValidateLoginAction 存储错误并且 returns "input"
- 正在重定向到 DisplayLoginAction..
- DisplayLoginAction 从拦截器中检索错误并将其显示给登录表单上方的用户
- 用户刷新页面
- 错误消失了
我应该如何保存页面刷新时的错误?
<action name="displayLoginPage" class="DisplayLoginAction">
<interceptor-ref name="store">
<param name="operationMode">RETRIEVE</param>
</interceptor-ref>
<interceptor-ref name="customStack"/>
<result name="success">Login.jsp</result>
<result name="input">Login.jsp</result>
</action>
<action name="validateloginForm" class="ValidateLoginAction">
<interceptor-ref name="store">
<param name="operationMode">STORE</param>
</interceptor-ref>
<interceptor-ref name="customStack"/>
<result name="input" type="redirectAction">displayLoginPage</result>
<result name="success">LoginConfirmation.jsp</result>
</action>
user refreshes the page
The errors are gone
How should I save the errors on page refresh?
这就是 MessageStoreInterceptor 的工作方式。它实际上是一个功能,而不是错误。
页面刷新是用户触发的动作,也就是说可以假设他已经读取了上一次操作(登录尝试)的结果,除非他是闭着眼睛按F5。
您应该希望消息在第一次阅读后过期。
考虑一个包含许多非ajax操作的页面,例如依赖于其他操作的组合框等...如果错误消息持续存在,它会在每次提交操作后弹出,不涉及去到另一页。
你不想要那个。您应该希望在 that 操作之后收到消息说一个操作出错(或正确)。如果用户随后继续执行其他操作,如刷新,如果这些操作没有出错(或处于特定的成功状态),则不应显示任何消息。
在那之后,还有 当 从会话中删除持久消息时的问题,因为否则您会在下一个操作中使用 RETRIEVE
或AUTOMATIC
操作模式。然后您可以(但不应该)自定义 MessageStore 拦截器,或者自行编写/读取/删除会话中的消息,基本上是重新发明轮子。或者甚至放两个MessageStore拦截器,一个RETRIEVE
和一个STORE
用于displayLoginPage
Action,得到刚刚提到的陷阱。
您还使用了PRG pattern (Post/Redirect/Get),以避免在刷新页面时重新发送数据。同样,您应该避免重复阅读相同的消息。
要了解具体是如何工作的,可以看一下MessageStore Interceptor source code,很简单:
- 调用前,如果 Action 是
ValidationAware
且 operationMode
是 RETRIEVE
或 AUTOMATIC
:
- 从会话中读取
actionMessages
、actionErrors
、fieldErrors
;
- 将它们与当前操作的
actionMessages
、actionErrors
、fieldErrors
; 合并
- 从会话中删除
actionMessages
、actionErrors
、fieldErrors
。
/**
* Handle the retrieving of field errors / action messages / field errors, which is
* done before action invocation, and the <code>operationMode</code> is 'RETRIEVE'.
*
* @param invocation
* @throws Exception
*/
protected void before(ActionInvocation invocation) throws Exception {
String reqOperationMode = getRequestOperationMode(invocation);
if (RETRIEVE_MODE.equalsIgnoreCase(reqOperationMode) ||
RETRIEVE_MODE.equalsIgnoreCase(operationMode) ||
AUTOMATIC_MODE.equalsIgnoreCase(operationMode)) {
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
// retrieve error / message from session
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
if (session == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Session is not open, no errors / messages could be retrieve for action ["+action+"]");
}
return;
}
ValidationAware validationAwareAction = (ValidationAware) action;
if (LOG.isDebugEnabled()) {
LOG.debug("retrieve error / message from session to populate into action ["+action+"]");
}
Collection actionErrors = (Collection) session.get(actionErrorsSessionKey);
Collection actionMessages = (Collection) session.get(actionMessagesSessionKey);
Map fieldErrors = (Map) session.get(fieldErrorsSessionKey);
if (actionErrors != null && actionErrors.size() > 0) {
Collection mergedActionErrors = mergeCollection(validationAwareAction.getActionErrors(), actionErrors);
validationAwareAction.setActionErrors(mergedActionErrors);
}
if (actionMessages != null && actionMessages.size() > 0) {
Collection mergedActionMessages = mergeCollection(validationAwareAction.getActionMessages(), actionMessages);
validationAwareAction.setActionMessages(mergedActionMessages);
}
if (fieldErrors != null && fieldErrors.size() > 0) {
Map mergedFieldErrors = mergeMap(validationAwareAction.getFieldErrors(), fieldErrors);
validationAwareAction.setFieldErrors(mergedFieldErrors);
}
session.remove(actionErrorsSessionKey);
session.remove(actionMessagesSessionKey);
session.remove(fieldErrorsSessionKey);
}
}
}
- 调用后,如果 Action 为
ValidationAware
且 operationMode
为 STORE
或(operationMode
为 AUTOMATIC
且 Result 类型为 redirectAction
):
- 阅读
actionMessages
, actionErrors
, fieldErrors
来自行动;
- 存储
actionMessages
, actionErrors
, fieldErrors
在会话中。
/**
* Handle the storing of field errors / action messages / field errors, which is
* done after action invocation, and the <code>operationMode</code> is in 'STORE'.
*
* @param invocation
* @param result
* @throws Exception
*/
protected void after(ActionInvocation invocation, String result) throws Exception {
String reqOperationMode = getRequestOperationMode(invocation);
boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
STORE_MODE.equalsIgnoreCase(operationMode) ||
(AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
// store error / messages into session
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
if (session == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Could not store action ["+action+"] error/messages into session, because session hasn't been opened yet.");
}
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("store action ["+action+"] error/messages into session ");
}
ValidationAware validationAwareAction = (ValidationAware) action;
session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
}
else if(LOG.isDebugEnabled()) {
LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
}
}
}
注1: 登录操作也是不言自明的:如果登录后再次登陆登录页面,只能说明登录失败了... BTW上面的解释适用每个 page/functionality
注意 2:有些网站生成的消息会在 X 秒后自动过期,不关心用户是否阅读过它们...并且 that不利于可用性,恕我直言。
考虑一下:
- 用户点击了 link
- 请求转到 DisplayLoginAction
- 显示Login.jsp
- 用户输入他的凭据
- 表单映射到 ValidateLoginAction
- ValidateLoginAction 中的验证失败
- ValidateLoginAction 存储错误并且 returns "input"
- 正在重定向到 DisplayLoginAction..
- DisplayLoginAction 从拦截器中检索错误并将其显示给登录表单上方的用户
- 用户刷新页面
- 错误消失了
我应该如何保存页面刷新时的错误?
<action name="displayLoginPage" class="DisplayLoginAction">
<interceptor-ref name="store">
<param name="operationMode">RETRIEVE</param>
</interceptor-ref>
<interceptor-ref name="customStack"/>
<result name="success">Login.jsp</result>
<result name="input">Login.jsp</result>
</action>
<action name="validateloginForm" class="ValidateLoginAction">
<interceptor-ref name="store">
<param name="operationMode">STORE</param>
</interceptor-ref>
<interceptor-ref name="customStack"/>
<result name="input" type="redirectAction">displayLoginPage</result>
<result name="success">LoginConfirmation.jsp</result>
</action>
user refreshes the page
The errors are gone
How should I save the errors on page refresh?
这就是 MessageStoreInterceptor 的工作方式。它实际上是一个功能,而不是错误。
页面刷新是用户触发的动作,也就是说可以假设他已经读取了上一次操作(登录尝试)的结果,除非他是闭着眼睛按F5。
您应该希望消息在第一次阅读后过期。
考虑一个包含许多非ajax操作的页面,例如依赖于其他操作的组合框等...如果错误消息持续存在,它会在每次提交操作后弹出,不涉及去到另一页。
你不想要那个。您应该希望在 that 操作之后收到消息说一个操作出错(或正确)。如果用户随后继续执行其他操作,如刷新,如果这些操作没有出错(或处于特定的成功状态),则不应显示任何消息。
在那之后,还有 当 从会话中删除持久消息时的问题,因为否则您会在下一个操作中使用 RETRIEVE
或AUTOMATIC
操作模式。然后您可以(但不应该)自定义 MessageStore 拦截器,或者自行编写/读取/删除会话中的消息,基本上是重新发明轮子。或者甚至放两个MessageStore拦截器,一个RETRIEVE
和一个STORE
用于displayLoginPage
Action,得到刚刚提到的陷阱。
您还使用了PRG pattern (Post/Redirect/Get),以避免在刷新页面时重新发送数据。同样,您应该避免重复阅读相同的消息。
要了解具体是如何工作的,可以看一下MessageStore Interceptor source code,很简单:
- 调用前,如果 Action 是
ValidationAware
且operationMode
是RETRIEVE
或AUTOMATIC
:- 从会话中读取
actionMessages
、actionErrors
、fieldErrors
; - 将它们与当前操作的
actionMessages
、actionErrors
、fieldErrors
; 合并
- 从会话中删除
actionMessages
、actionErrors
、fieldErrors
。
- 从会话中读取
/**
* Handle the retrieving of field errors / action messages / field errors, which is
* done before action invocation, and the <code>operationMode</code> is 'RETRIEVE'.
*
* @param invocation
* @throws Exception
*/
protected void before(ActionInvocation invocation) throws Exception {
String reqOperationMode = getRequestOperationMode(invocation);
if (RETRIEVE_MODE.equalsIgnoreCase(reqOperationMode) ||
RETRIEVE_MODE.equalsIgnoreCase(operationMode) ||
AUTOMATIC_MODE.equalsIgnoreCase(operationMode)) {
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
// retrieve error / message from session
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
if (session == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Session is not open, no errors / messages could be retrieve for action ["+action+"]");
}
return;
}
ValidationAware validationAwareAction = (ValidationAware) action;
if (LOG.isDebugEnabled()) {
LOG.debug("retrieve error / message from session to populate into action ["+action+"]");
}
Collection actionErrors = (Collection) session.get(actionErrorsSessionKey);
Collection actionMessages = (Collection) session.get(actionMessagesSessionKey);
Map fieldErrors = (Map) session.get(fieldErrorsSessionKey);
if (actionErrors != null && actionErrors.size() > 0) {
Collection mergedActionErrors = mergeCollection(validationAwareAction.getActionErrors(), actionErrors);
validationAwareAction.setActionErrors(mergedActionErrors);
}
if (actionMessages != null && actionMessages.size() > 0) {
Collection mergedActionMessages = mergeCollection(validationAwareAction.getActionMessages(), actionMessages);
validationAwareAction.setActionMessages(mergedActionMessages);
}
if (fieldErrors != null && fieldErrors.size() > 0) {
Map mergedFieldErrors = mergeMap(validationAwareAction.getFieldErrors(), fieldErrors);
validationAwareAction.setFieldErrors(mergedFieldErrors);
}
session.remove(actionErrorsSessionKey);
session.remove(actionMessagesSessionKey);
session.remove(fieldErrorsSessionKey);
}
}
}
- 调用后,如果 Action 为
ValidationAware
且operationMode
为STORE
或(operationMode
为AUTOMATIC
且 Result 类型为redirectAction
):- 阅读
actionMessages
,actionErrors
,fieldErrors
来自行动; - 存储
actionMessages
,actionErrors
,fieldErrors
在会话中。
- 阅读
/**
* Handle the storing of field errors / action messages / field errors, which is
* done after action invocation, and the <code>operationMode</code> is in 'STORE'.
*
* @param invocation
* @param result
* @throws Exception
*/
protected void after(ActionInvocation invocation, String result) throws Exception {
String reqOperationMode = getRequestOperationMode(invocation);
boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
STORE_MODE.equalsIgnoreCase(operationMode) ||
(AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
// store error / messages into session
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
if (session == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Could not store action ["+action+"] error/messages into session, because session hasn't been opened yet.");
}
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("store action ["+action+"] error/messages into session ");
}
ValidationAware validationAwareAction = (ValidationAware) action;
session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
}
else if(LOG.isDebugEnabled()) {
LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
}
}
}
注1: 登录操作也是不言自明的:如果登录后再次登陆登录页面,只能说明登录失败了... BTW上面的解释适用每个 page/functionality
注意 2:有些网站生成的消息会在 X 秒后自动过期,不关心用户是否阅读过它们...并且 that不利于可用性,恕我直言。