struts2 使用消息存储拦截器进行验证

struts2 validation using message store interceptor

考虑一下:

  1. 用户点击了 link
  2. 请求转到 DisplayLoginAction
  3. 显示Login.jsp
  4. 用户输入他的凭据
  5. 表单映射到 ValidateLoginAction
  6. ValidateLoginAction 中的验证失败
  7. ValidateLoginAction 存储错误并且 returns "input"
  8. 正在重定向到 DisplayLoginAction..
  9. DisplayLoginAction 从拦截器中检索错误并将其显示给登录表单上方的用户
  10. 用户刷新页面
  11. 错误消失了

我应该如何保存页面刷新时的错误?

<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>
  1. user refreshes the page

  2. The errors are gone

How should I save the errors on page refresh?

这就是 MessageStoreInterceptor 的工作方式。它实际上是一个功能,而不是错误。

页面刷新是用户触发的动作,也就是说可以假设他已经读取了上一次操作(登录尝试)的结果,除非他是闭着眼睛按F5。

您应该希望消息在第一次阅读后过期。

考虑一个包含许多非ajax操作的页面,例如依赖于其他操作的组合框等...如果错误消息持续存在,它会在每次提交操作后弹出,不涉及去到另一页。

你不想要那个。您应该希望在 that 操作之后收到消息说一个操作出错(或正确)。如果用户随后继续执行其他操作,如刷新,如果这些操作没有出错(或处于特定的成功状态),则不应显示任何消息。

在那之后,还有 从会话中删除持久消息时的问题,因为否则您会在下一个操作中使用 RETRIEVEAUTOMATIC 操作模式。然后您可以(但不应该)自定义 MessageStore 拦截器,或者自行编写/读取/删除会话中的消息,基本上是重新发明轮子。或者甚至放两个MessageStore拦截器,一个RETRIEVE和一个STORE用于displayLoginPage Action,得到刚刚提到的陷阱。

您还使用了PRG pattern (Post/Redirect/Get),以避免在刷新页面时重新发送数据。同样,您应该避免重复阅读相同的消息。

要了解具体是如何工作的,可以看一下MessageStore Interceptor source code,很简单:

  • 调用前,如果 Action 是 ValidationAwareoperationModeRETRIEVEAUTOMATIC
    1. 从会话中读取 actionMessagesactionErrorsfieldErrors
    2. 将它们与当前操作的 actionMessagesactionErrorsfieldErrors;
    3. 合并
    4. 从会话中删除actionMessagesactionErrorsfieldErrors
/**
 * 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 为 ValidationAwareoperationModeSTORE 或(operationModeAUTOMATIC 且 Result 类型为 redirectAction):
    1. 阅读 actionMessages, actionErrors, fieldErrors 来自行动;
    2. 存储 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不利于可用性,恕我直言。