ViewExpiredException發生時,如何恢復View視圖

javax.faces.application.ViewExpiredException: View could not be restored
ViewExpiredException發生時,View視圖無法恢復




1. web.xml 中, STATE_SAVING_METHOD 設置爲 server


1.5 當用戶從一個view中<h:form>表單中使用HTTP POST方法(<h:commandLink>, <h:commandButton> 或者 <f:ajax>)
請求服務時,Session中的相應的view state不存在,此時ViewExpiredException被拋出。 


2. <h:form> 表單中隱藏的變量 javax.faces.ViewState 是一個ID,它與Session中serialized view state 相互對應,
    當Session由於某種原因(Server 或 Client 端的time out , 或者 瀏覽器中Session cookie 不存在了,
或者Server端的HttpSession#invalidate()被呼叫,或者Server端的bug,導致 session cookie被認定爲WildFly), 
導致Session中的serialized view state 不存在時,客戶將得到這個exception,ViewExpiredException 將被拋出。
  
3. JSF的views的數量有一個上限,當這個上限被觸及,最少瀏覽的view(least recently used view )將被失效。參考
 com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews.


4.當STATE_SAVING_METHOD設定爲 client時,javax.faces.ViewState中隱藏的輸入量沒有值,當Session expires,客戶端無法獲取
ViewExpiredException.這種情形也會發生在 Cluster的環境下("ERROR: MAC did not verify" is symptomatic),或者當
Client端實現了特製的state timeout的情況,或者Server端重新啓動導致AES密鑰重新生成的情況時。


避免在page navigation時出現ViewExpiredException


1. In order to avoid ViewExpiredException when e.g. navigating back after logout when the state 
saving is set to server, only redirecting the POST request after logout is not sufficient. 
You also need to instruct the browser to not cache the dynamic JSF pages, otherwise the browser 
may show them from the cache instead of requesting a fresh one from the server when you send 
a GET request on it (e.g. by back button).
比如,在STATE_SAVING_METHOD 設置爲 server時,當用戶logout後,點擊navigating back,
爲了避免出現ViewExpiredException,實施POST請求的redirect是沒有效果的。你必須還要
設置browser對dynamic JSF頁面不實施cache緩存,否則,當你發送一個GET請求(例如back button)後,
browser將顯示cache中的緩存頁面,而不從server中刷取新的頁面。




2. The javax.faces.ViewState hidden field of the cached page may contain a view state ID value which 
is not valid anymore in the current session. If you're (ab)using POST (command links/buttons) instead 
of GET (regular links/buttons) for page-to-page navigation, and click such a command link/button on the 
cached page, then this will in turn fail with a ViewExpiredException.


Cached page中的javax.faces.ViewState的隱藏變量中包含着一個view state ID,這個ID在當前的session中不再有效。
如果你用POST(command links/buttons)代替GET (regular links/buttons) 實施page 的navigation,比如點擊 
cached page 中的command link/button,這時由於ViewExpiredException的原因頁面跳轉將失敗。


To fire a redirect after logout in JSF 2.0, either add <redirect /> to the <navigation-case> in question 
(if any), or add ?faces-redirect=true to the outcome value.


爲了在JSF2.0中,在用戶logout後觸發一個redirect,可以在涉及的<navigation-case>中添加<redirect />,或者在outcome
中添加?faces-redirect=true。


例如<h:commandButton value="Logout" action="logout?faces-redirect=true" />  


或者


public String logout() {
    // ...
    return "index?faces-redirect=true";
}


3. To instruct the browser to not cache the dynamic JSF pages, create a Filter which is mapped on the servlet
 name of the FacesServlet and adds the needed response headers to disable the browser cache. E.g.


爲了將browser設置爲不cache動態JSF頁面,可以依據FacesServlet的servlet創建一個Filter, 添加相應的response headers 以便關閉
browser的cache屬性。
 


@WebFilter(servletNames={"Faces Servlet"}) // Must match <servlet-name> of your FacesServlet.
public class NoCacheFilter implements 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);
    }


    // ...
}




Avoiding ViewExpiredException on page refresh
頁面刷新過程中避免ViewExpiredException


In order to avoid ViewExpiredException when refreshing the current page when the state saving is set to server, 
you not only need to make sure you are performing page-to-page navigation exclusively by GET (regular links/buttons), 
but you also need to make sure that you are exclusively using ajax to submit the forms. If you're submitting the 
form synchronously (non-ajax) anyway, then you'd best either make the view stateless (see later section), or to 
send a redirect after POST (see previous section).


1. 在STATE_SAVING_METHOD 設置爲 server情況下,爲了避免頁面刷新過程中發生ViewExpiredException,
你不但要確保頁面跳轉不使用GET (regular links/buttons),而且還要確保forms的請求不使用ajax方式。如果你提交同步(非ajax)請求,
你最好將view設置爲stateless,或者在POST後發送一個redirect(見上一節) 


Having a ViewExpiredException on page refresh is in default configuration a very rare case. It can only happen when the limit 
on the amount of views JSF will store in the session is hit. So, it will only happen when you've manually set that limit way 
too low, or that you're continuously creating new views in background (e.g. by a badly implemented poll). 
See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews. Another cause is having duplicate 
JSF libraries in runtime classpath conflicting each other. The correct procedure to install JSF is outlined in our JSF wiki page.


2.缺省配置情況下,在頁面刷新時發生ViewExpiredException意外是很罕見的。當Session中JSF view的數量達到上限值時纔會發生。因此,
只有當手工設置這個上限值過低時,或者在後臺不斷地創建新的views時(比如,錯誤的實現poll). 另一個原因是在runtime的classpath 存在重複
的JSF libraries,彼此相關干擾。




Handling ViewExpiredException
處理ViewExpiredException




When you want to handle an unavoidable ViewExpiredException after a POST action on an arbitrary page which was already opened
 in some browser tab/window while you're logged out in another tab/window, then you'd like to specify an error-page for that 
in web.xml which goes to a "Your session is timed out" page. E.g.


1.當你想在一個瀏覽器中的tab/window打開任意的頁面,產生一個POST請求後,處理無法避免的ViewExpiredException時,你最好在web.xml中
聲明一個error-page,跳轉到一個"Your session is timed out"頁面。例如:




<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>


2.Use if necessary a meta refresh header in the error page in case you intend to actually redirect further to home or login page.
如果需要,在error page中使用meta refresh header,這樣你可以跳轉到你希望的home或login頁面。


<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Session expired</title>
        <meta http-equiv="refresh" content="0;url=#{request.contextPath}/login.xhtml" />
    </head>
    <body>
        <h1>Session expired</h1>
        <h3>You will be redirected to login page</h3>
        <p><a href="#{request.contextPath}/login.xhtml">Click here if redirect didn't work or when you're impatient</a>.</p>
    </body>
</html>




(the 0 in content represents the amount of seconds before redirect, 0 thus means "redirect immediately", you can use e.g. 3 to let the browser wait 3 seconds with the redirect)


Note that handling exceptions during ajax requests requires a special ExceptionHandler. See also Session timeout and 
ViewExpiredException handling on JSF/PrimeFaces ajax request. You can find a live example at OmniFaces FullAjaxExceptionHandler 
showcase page (this also covers non-ajax requests).


3.注意的是,在ajax請求時,處理exceptions需要一個特定的ExceptionHandler.可以參考JSF/PrimeFaces ajax request中對ViewExpiredException的處理方法。
也可以找個一個 OmniFaces中的FullAjaxExceptionHandler的例子(也包括了non-ajax requests)。




Also note that your "general" error page should be mapped on <error-code> of 500 instead of an <exception-type> of e.g. java.lang.Exception 
or java.lang.Throwable, otherwise all exceptions wrapped in ServletException such as ViewExpiredException would still end up in 
the general error page. See also ViewExpiredException shown in java.lang.Throwable error-page in web.xml.


4.注意的是,你的“通用”error page需要和 500的<error-code> 做匹配,而不是<exception-type>設置爲java.lang.Exception 或者
java.lang.Throwable。此外,所有的ServletException,比如 ViewExpiredException都要在通用的error page中設置。可以參看在web.xml中的
java.lang.Throwable error-page中的ViewExpiredException 。


<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>




Stateless views
無狀態views


A completely different alternative is to run JSF views in stateless mode. This way nothing of JSF state will 
be saved and the views will never expire, but just be rebuilt from scratch on every request. You can turn on 
stateless views by setting the transient attribute of <f:view> to true:
另一種方法是將JSF view運行在無狀態模式。這種方式下,JSF state將不做存儲,因此views也不會過期,但是會因爲request
重新構建。你可以將<f:view>的短暫狀態設置爲true:


<f:view transient="true">


</f:view>


This way the javax.faces.ViewState hidden field will get a fixed value of "stateless" in Mojarra 
(have not checked MyFaces at this point). Note that this feature was introduced in Mojarra 2.1.19 and 2.2.0 
and is not available in older versions.


The consequence is that you cannot use view scoped beans anymore. They will now behave like request scoped 
beans. One of the disadvantages is that you have to track the state yourself by fiddling with hidden inputs 
and/or loose request parameters. Mainly those forms with input fields with rendered, readonly or disabled 
attributes which are controlled by ajax events will be affected.


這樣做法的效果是,你不能再使用view scoped beans。這些bean不能夠再以request scoped beans的形式運行。這樣做的
劣勢是你無法依據隱藏的inputs和/或者鬆散的request parameters輕鬆地追蹤state狀態。這些表單forms,其輸入變量的渲染
、只讀或關閉的屬性,這些由ajax事件改變的屬性的表單將會受到影響。


Note that the <f:view> does not necessarily need to be unique throughout the view and/or reside in the master
 template only. It's also completely legit to redeclare and nest it in a template client. 
It basically "extends" the parent <f:view> then. E.g. in master template:


注意的是,<f:view>在view的生命週期中,和/或者以master template方式構建,不是必須要保持唯一的。原則上它可以
“extends”父類<f:view>。例如,在master template中:


<f:view contentType="text/html">
    <ui:insert name="content" />
</f:view>
and in template client:


<ui:define name="content">
    <f:view transient="true">
        <h:form>...</h:form>
    </f:view>
</f:view>


You can even wrap the <f:view> in a <c:if> to make it conditional. Note that it would apply on the entire 
view, not only on the nested contents, such as the <h:form> in above example.


您可以在<c:if>中嵌入<f:view>,可以做些條件設定。注意的是,它將在view中一直有效,包括在嵌套的內容中,
例如在上面例子中<h:form>...</h:form>的部分也有效。









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章