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>的部分也有效。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Android Studio中,可以使用视图绑定(ViewBinding)来替代findViewById方法,以更方便地访问和操作视图元素。下面是使用视图绑定实现视图的步骤: 1. 确保你的项目已经升级到Android Studio 3.6 Canary 11或更高版本。 2. 在项目的build.gradle文件中,将以下代码添加到android块中: ```groovy viewBinding { enabled = true } ``` 3. 在布局文件中,使用<layout>标签将布局文件包裹起来,例如: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 布局内容 --> </LinearLayout> </layout> ``` 4. 在Activity或Fragment中,使用以下代码来获取视图绑定实例: ```java // 对于Activity private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // 现在可以通过binding对象访问布局中的视图元素了 binding.textView.setText("Hello World!"); } // 对于Fragment private FragmentMainBinding binding; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentMainBinding.inflate(inflater, container, false); View rootView = binding.getRoot(); // 现在可以通过binding对象访问布局中的视图元素了 binding.textView.setText("Hello World!"); return rootView; } ``` 通过使用视图绑定,你可以直接使用布局文件中定义的视图元素,而无需手动调用findViewById方法。这样可以提高代码的可读性和开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值