【问题标题】:browser back + viewscope beans浏览器返回 + viewscope bean
【发布时间】:2011-12-25 02:59:30
【问题描述】:

问题是什么: 单击浏览器后退按钮时会发生什么情况 --> 打开一个其 viewscoped-managedbean 已被销毁的页面 --> 使用 grid-record-selections 从该页面提交来自 commandButton 的请求?

我的期望: 关联的 viewscope-managebean 被重新创建,接收 grid-record-selections,并像浏览器后退按钮一样处理它们。

我的经历: 未重新创建关联的 viewscope-managebean,不接收网格记录选择。必须重新输入 URL,或者单击浏览器返回按钮后 F5 才能再次正常工作。

所以这是成功场景,所有 bean 都是 viewscoped beans:

  1. GET page1.xhtml --> 在@PostConstruct 中创建page1Bean,查询数据等
  2. 从数据表中检查/选择多条记录,点击处理按钮
  3. page1Bean 的 process 方法将选中的记录保存在 flash 对象中,并重定向到 page2.xhtml
  4. page1Bean 销毁,page2Bean 创建,并在 preRenderView 监听器方法中,从 flash 对象中获取选中的记录,并进行处理
  5. 点击“转到主页面”命令按钮重定向到page1.xhtml,page2Bean销毁,page1Bean重新创建
  6. 从 2 号到 5 号循环仍然可行

现在,这是涉及浏览器后退按钮的错误场景(从 #6 开始发生不同的事情):

  1. GET page1.xhtml --> 在@PostConstruct 中创建page1Bean,查询数据等
  2. 从数据表中检查/选择多条记录,点击处理按钮
  3. page1Bean 的 process 方法将选中的记录保存在 flash 对象中,并重定向到 page2.xhtml
  4. page1Bean 销毁,page2Bean 创建,并在 preRenderView 监听器方法中,从 flash 对象中获取选中的记录,并进行处理
  5. 点击浏览器后退按钮 page2Bean 未销毁,page1Bean 未创建
  6. 从数据表中检查/选择多条记录,点击处理按钮
  7. page1Bean 方法执行(奇怪,因为 page1Bean 应该已被销毁),但看不到所做的记录选择,并重定向到 page2.xhtml
  8. page1Bean 没有被销毁(没有日志输出),page2Bean 没有被创建(因为它没有被销毁),像往常一样执行 preRenderView 监听器,但是这一次,flash 对象中没有选择记录

是否可以使用带有浏览器后退按钮的 viewscope-beans 获得正常体验(就像没有浏览器后退按钮一样)?

这是我的依赖:

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.faces</artifactId>
    <version>2.1.3</version>
    <scope>compile</scope>
</dependency>

请分享你的想法!

【问题讨论】:

    标签: jsf-2 back-button


    【解决方案1】:

    浏览器似乎从其缓存中为页面提供服务,而不是向服务器发送完整的 HTTP GET 请求,而您将 JSF 状态保存方法设置为 server(这是默认设置)。

    有两种方法可以解决这个问题:

    1. 告诉浏览器不要缓存动态 JSF 页面。您可以在 filter 的帮助下完成此操作。

      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
          HttpServletRequest req = (HttpServletRequest) request;
          HttpServletResponse res = (HttpServletResponse) response;
      
          if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
              res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
              res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
              res.setDateHeader("Expires", 0); // Proxies.
          }
      
          chain.doFilter(request, response);
      }
      

      将过滤器映射到 FacesServlet 或其相同的 URL 模式。

    2. 将JSF状态保存方式设置为客户端,使整个视图状态保存在表单的隐藏字段中,而不是服务器端的会话中。

      <context-param>
          <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
          <param-value>client</param-value>
      </context-param>
      

    过滤方式更可取。

    【讨论】:

    • 感谢源示例 :) 我尝试将状态保存方法更改为客户端,我有点吓坏了,我必须将许多 bean 和域 bean 更改为可序列化。所以,尝试了过滤器,效果很好!尽管这里的这篇文章有点担心:turbomanage.wordpress.com/2006/08/08/… 声明不能保证。但根据我在这里的经验,它运作良好。
    • PhaseListener 中这样做很笨拙。至于保证,好吧,他说得有道理,但是所有现代浏览器(IE、FF、GC、AS、O 等)都正确地尊重 HTTP 缓存规则。只有当客户端使用一些可疑的浏览器时,它才可能会失败。
    • 但是有一点,当点击浏览器的返回按钮时,p:datatable里面被选中的复选框仍然被选中,但是在提交时,它们实际上并没有被选中。这发生在 google chrome 15 中,但不是 Firefox 5.0.1。我认为情况与此相同:stackoverflow.com/questions/6100741/…。我想尝试使用解决方案中所述的随机表单名称,但如果我不知道如何刷新具有多个表单的页面中的表单内的另一个表单外部形式的名称。
    • 这确实是特定于浏览器的。您需要将autocomplete="off" 添加到&lt;form&gt;。不幸的是,&lt;h:form&gt; 不支持此功能,您可能希望为此使用自定义的UIForm 渲染器或一些 JS/jQuery,或者只是将其添加到单个输入元素中。另见developer.mozilla.org/en/How_to_Turn_Off_Form_Autocompletion
    【解决方案2】:

    禁用页面的浏览器缓存的缺点是,如果用户使用浏览器返回导航到上一页,则会看到浏览器错误页面。 所以另一种解决方案是使用javascript识别页面是来自服务器还是来自浏览器缓存:

    首先创建一个提供唯一 id 的简单支持 bean(在我的例子中是当前系统时间):

    @Named("browserCacheController")
    @RequestScoped
    public class BrowserCacheController implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * Returns a unique increasing id for each request
     * @return
     */
    public long getCacheID() {
        return System.currentTimeMillis();
    }   
    }
    

    因此,现在您可以测试页面是从服务器还是浏览器提供,如果当前页面来自浏览器缓存,则重定向用户。请参阅以下放置在浏览器不应缓存的 jsf 页面中的 javascript 代码:

    <script type="text/javascript">
        // check for latestCacheID
        if (!isValidCacheID(#{browserCacheController.cacheID})) {
            //redirect to some page
            document.location="#{facesContext.externalContext.requestContextPath}/index.jsf";
        }
    
        // test cacheID if it comes from the server....
        function isValidCacheID(currentCacheID) {
            if (!('localStorage' in window && window['localStorage'] !== null))
                return true; // old browsers not supported
            var latestCacheID=localStorage.getItem("org.imixs.latestCacheID");
            if (latestCacheID!=null && currentCacheID<=latestCacheID) {
                return false; // this was a cached browser page!
            }
            // set new id
            localStorage.setItem("org.imixs.latestCacheID", currentCacheID);
            return true;
        }
    </script>
    

    脚本也可以放到facelet中,让jsf代码更干净。

    【讨论】:

    • 你不应该使用 POST 来导航。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-06
    • 2012-10-10
    相关资源
    最近更新 更多