บทนำ
ViewExpiredException
จะถูกโยนเมื่อjavax.faces.STATE_SAVING_METHOD
มีการตั้งค่าserver
(เริ่มต้น) และ enduser จะส่งคำขอ HTTP POST ในมุมมองทาง<h:form>
ด้วย<h:commandLink>
, <h:commandButton>
หรือ<f:ajax>
ในขณะที่มุมมองของรัฐที่เกี่ยวข้องไม่สามารถใช้ได้ในเซสชั่นอีกต่อไป
สภาวะมุมมองที่ถูกระบุว่าเป็นค่าของช่องใส่ซ่อนของjavax.faces.ViewState
<h:form>
ด้วยการตั้งค่าวิธีการบันทึกสถานะนี้จะserver
มีเพียง ID สถานะมุมมองที่อ้างอิงถึงสถานะมุมมองแบบอนุกรมในเซสชัน ดังนั้นเมื่อเซสชั่นหมดอายุด้วยเหตุผลบางอย่าง (หมดเวลาในฝั่งเซิร์ฟเวอร์หรือลูกค้าหรือคุกกี้เซสชั่นจะไม่รักษาอีกต่อไปด้วยเหตุผลบางอย่างในเบราว์เซอร์หรือโดยการโทรHttpSession#invalidate()
ในเซิร์ฟเวอร์หรือเนื่องจากข้อผิดพลาดเฉพาะเซิร์ฟเวอร์กับคุกกี้เซสชัน รู้จักกันในWildFly ) จากนั้นสถานะการดูต่อเนื่องจะไม่สามารถใช้ได้อีกในเซสชันและ enduser จะได้รับข้อยกเว้นนี้ เพื่อให้เข้าใจถึงการทำงานของเซสชันดูที่servlets ทำงานอย่างไร instantiation เซสชันตัวแปรที่ใช้ร่วมกันและมัลติเธรด
นอกจากนี้ยังมีข้อ จำกัด เกี่ยวกับจำนวนการดูที่ JSF จะเก็บไว้ในเซสชัน เมื่อถึงขีด จำกัด แล้วการดูที่น้อยที่สุดที่ใช้ล่าสุดจะหมดอายุ ดูเพิ่มเติมcom.sun.faces.numberOfViewsInSession VS com.sun.faces.numberOfLogicalViews
กับการประหยัดวิธีการตั้งค่าไปยังรัฐclient
ที่javax.faces.ViewState
ช่องป้อนซ่อนมีแทนทั้งสถานะของมุมมองต่อเนื่องดังนั้น enduser จะไม่ได้รับViewExpiredException
เมื่อช่วงหมดอายุ อย่างไรก็ตามยังสามารถเกิดขึ้นได้ในสภาพแวดล้อมแบบคลัสเตอร์ ("ข้อผิดพลาด: MAC ไม่ได้ตรวจสอบ" เป็นอาการ) และ / หรือเมื่อมีการหมดเวลาสำหรับการนำไปใช้งานเฉพาะในสถานะฝั่งไคลเอ็นต์ที่กำหนดค่าและ / หรือเมื่อเซิร์ฟเวอร์สร้างคีย์ AES ขึ้นใหม่ระหว่างการรีสตาร์ท ดูที่การรับ ViewExpiredException ในสภาพแวดล้อมแบบคลัสเตอร์ในขณะที่วิธีการบันทึกสถานะถูกตั้งค่าเป็นไคลเอนต์และเซสชันผู้ใช้ที่ถูกต้องวิธีการแก้ปัญหา
โดยไม่คำนึงถึงวิธีการแก้ปัญหาให้แน่ใจว่าคุณจะไม่ได้enableRestoreView11Compatibility
ใช้ มันไม่ได้เลยคืนสถานะมุมมองเดิม โดยทั่วไปจะสร้างมุมมองใหม่และมุมมองที่เกี่ยวข้องทั้งหมดที่กำหนดขอบเขตจากจุดเริ่มต้นและด้วยเหตุนี้จึงสูญเสียข้อมูลดั้งเดิมทั้งหมด (สถานะ) เนื่องจากแอปพลิเคชันจะทำงานในลักษณะที่สับสน ("เฮ้ค่าการป้อนข้อมูลของฉันอยู่ที่ไหน .. ??") นี่เป็นสิ่งที่แย่มากสำหรับประสบการณ์ของผู้ใช้ ใช้มุมมองแบบไร้รัฐหรือดีกว่า<o:enableRestorableView>
แทนเพื่อให้คุณสามารถจัดการได้ในมุมมองเฉพาะเท่านั้นแทนที่จะเป็นในทุกมุมมอง
สำหรับสาเหตุที่ JSF ต้องการบันทึกสถานะมุมมองให้ตอบคำถามนี้: เหตุใด JSF จึงบันทึกสถานะขององค์ประกอบ UI บนเซิร์ฟเวอร์
หลีกเลี่ยง ViewExpiredException ในการนำทางหน้า
เพื่อหลีกเลี่ยงViewExpiredException
เมื่อเช่นการนำทางกลับหลังจากออกจากระบบเมื่อการตั้งค่าการบันทึกสถานะเป็นserver
เพียงการเปลี่ยนเส้นทางคำขอ POST หลังจากออกจากระบบไม่เพียงพอ คุณต้องสั่งให้เบราว์เซอร์ไม่แคชเพจ JSF แบบไดนามิกมิฉะนั้นเบราว์เซอร์อาจแสดงเพจจากแคชแทนการร้องขอเพจใหม่จากเซิร์ฟเวอร์เมื่อคุณส่งคำขอ GET (เช่นโดยปุ่มย้อนกลับ)
javax.faces.ViewState
ข้อมูลที่ซ่อนอยู่ของหน้าที่เก็บไว้อาจมีค่า ID มุมมองของรัฐที่ไม่ถูกต้องอีกต่อไปในเซสชั่นปัจจุบัน หากคุณ (ab) ใช้ POST (ลิงก์คำสั่ง / ปุ่ม) แทน GET (ลิงก์ / ปุ่มปกติ) สำหรับการนำทางแบบหน้าต่อหน้าและคลิกลิงก์คำสั่ง / ปุ่มดังกล่าวบนหน้าแคชจากนั้นจะเป็นการเปิด ViewExpiredException
ล้มเหลวด้วย
ที่จะยิงเปลี่ยนเส้นทางหลังจากที่ออกจากระบบใน JSF 2.0 เพิ่ม<redirect />
ไป<navigation-case>
ในคำถาม (ถ้ามี) หรือเพิ่ม?faces-redirect=true
ไปยังoutcome
ค่า
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
หรือ
public String logout() {
// ...
return "index?faces-redirect=true";
}
หากต้องการแนะนำให้เบราว์เซอร์ไม่แคชเพจ JSF แบบไดนามิกให้สร้างFilter
ที่แม็พไว้บนชื่อเซิร์ฟเล็ตของFacesServlet
และเพิ่มส่วนหัวการตอบกลับที่จำเป็นเพื่อปิดใช้งานแคชเบราว์เซอร์ เช่น
@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);
}
// ...
}
หลีกเลี่ยง ViewExpiredException ในการรีเฟรชหน้า
เพื่อหลีกเลี่ยงViewExpiredException
เมื่อรีเฟรชหน้าปัจจุบันเมื่อตั้งค่าการบันทึกสถานะไว้server
คุณไม่เพียง แต่ต้องแน่ใจว่าคุณกำลังทำการนำทางแบบหน้าต่อหน้าโดย GET (ลิงก์ / ปุ่มปกติ) แต่คุณต้องแน่ใจด้วย คุณใช้ ajax เพื่อส่งแบบฟอร์มโดยเฉพาะ หากคุณกำลังส่งแบบฟอร์มพร้อมกัน (ไม่ใช่อาแจ็กซ์) คุณควรกำหนดให้เป็นมุมมองแบบไร้รัฐ (ดูหัวข้อภายหลัง) หรือส่งการเปลี่ยนเส้นทางหลังจาก POST (ดูหัวข้อก่อนหน้า)
การViewExpiredException
รีเฟรชบนหน้าเป็นการกำหนดค่าเริ่มต้นเป็นกรณีที่หายากมาก มันสามารถเกิดขึ้นได้เมื่อมีการ จำกัด จำนวนการดู JSF ที่จะจัดเก็บในเซสชัน ดังนั้นจะเกิดขึ้นก็ต่อเมื่อคุณตั้งค่าขีด จำกัด นั้นต่ำเกินไปหรือว่าคุณกำลังสร้างมุมมองใหม่ใน "พื้นหลัง" อย่างต่อเนื่อง (เช่นโดยโพลอาแจ็กซ์ที่นำไปใช้อย่างไม่ดีในหน้าเดียวกัน หน้าข้อผิดพลาดเกี่ยวกับภาพที่แตกสลายในหน้าเดียวกัน) ดูเพิ่มเติมที่com.sun.faces.numberOfViewsInSession เทียบกับ com.sun.faces.numberOfLogicalViewsเพื่อดูรายละเอียดเกี่ยวกับขีด จำกัด นั้น อีกสาเหตุคือการมีไลบรารี JSF ที่ซ้ำกันใน classpath รันไทม์ที่ขัดแย้งกัน ขั้นตอนที่ถูกต้องในการติดตั้ง JSF จะระบุไว้ในหน้าวิกิพีเดีย JSF ของเรา
การจัดการ ViewExpiredException
เมื่อคุณต้องการจัดการกับสิ่งที่หลีกเลี่ยงไม่ได้ViewExpiredException
หลังจากการกระทำ POST บนหน้าเว็บที่เปิดขึ้นแล้วในแท็บ / หน้าต่างเบราว์เซอร์บางอันในขณะที่คุณออกจากระบบในแท็บ / หน้าต่างอื่นแล้วคุณต้องการระบุerror-page
สิ่งweb.xml
ที่จะไป ไปที่หน้า "เซสชั่นของคุณหมดเวลา" เช่น
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
ใช้หากจำเป็นส่วนหัวของการรีเฟรชเมตาในหน้าข้อผิดพลาดในกรณีที่คุณต้องการเปลี่ยนเส้นทางไปยังหน้าแรกหรือหน้าเข้าสู่ระบบจริง
<!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>
( 0
ในcontent
หมายถึงจำนวนวินาทีก่อนที่จะเปลี่ยนเส้นทางซึ่ง0
หมายความว่า "เปลี่ยนเส้นทางทันที" คุณสามารถใช้เช่น3
เพื่อให้เบราว์เซอร์รอ 3 วินาทีด้วยการเปลี่ยนเส้นทาง)
ExceptionHandler
โปรดทราบว่าการจัดการข้อยกเว้นในระหว่างการร้องขออาแจ็กซ์ต้องพิเศษ ดูเพิ่มเติมหมดเวลาเซสชันและการจัดการ ViewExpiredException ใน JSF / PrimeFaces คำขออาแจ็กซ์ คุณสามารถค้นหาตัวอย่างสดได้ที่หน้าแสดงOmniFacesFullAjaxExceptionHandler
(ซึ่งรวมถึงคำขอที่ไม่ใช่อาแจ็กซ์ )
นอกจากนี้ทราบว่า "ทั่วไป" หน้าข้อผิดพลาดของคุณควรจะแมปบน<error-code>
ของ500
แทน<exception-type>
ของเช่นjava.lang.Exception
หรือjava.lang.Throwable
มิฉะนั้นข้อยกเว้นทั้งหมดห่อServletException
เช่นViewExpiredException
ยังคงจบลงในหน้าข้อผิดพลาดทั่วไป ดูเพิ่มเติมViewExpiredException แสดงใน java.lang.Throwable ข้อผิดพลาดหน้าใน web.xml
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
มุมมองไร้สัญชาติ
ทางเลือกที่แตกต่างอย่างสิ้นเชิงคือการรันมุมมอง JSF ในโหมดไร้สัญชาติ วิธีนี้จะไม่มีการบันทึกสถานะ JSF ใด ๆ และมุมมองจะไม่มีวันหมดอายุ แต่เพิ่งสร้างใหม่ตั้งแต่เริ่มต้นในทุกคำขอ คุณสามารถเปิดมุมมองไร้สัญชาติได้โดยตั้งค่าtransient
แอตทริบิวต์ของ<f:view>
เป็นtrue
:
<f:view transient="true">
</f:view>
วิธีjavax.faces.ViewState
นี้ฟิลด์ที่ซ่อนจะได้รับค่าคงที่"stateless"
ใน Mojarra (ยังไม่ได้ตรวจสอบ MyFaces ณ จุดนี้) โปรดทราบว่าฟีเจอร์นี้เปิดตัวใน Mojarra 2.1.19 และ 2.2.0 และไม่สามารถใช้ได้ในเวอร์ชั่นที่เก่ากว่า
ผลที่ตามมาคือคุณไม่สามารถใช้ถั่วที่ถูกกำหนดมุมมองได้อีกต่อไป ตอนนี้พวกเขาจะทำตัวเหมือนถั่วที่ถูกกำหนดขอบเขตการร้องขอ ข้อเสียอย่างหนึ่งคือคุณต้องติดตามสถานะด้วยตัวคุณเองโดยเล่นซอกับอินพุตที่ซ่อนอยู่และ / หรือพารามิเตอร์คำขอหลวม ส่วนใหญ่เป็นรูปแบบที่มีช่องใส่กับrendered
, readonly
หรือdisabled
แอตทริบิวต์ซึ่งถูกควบคุมโดยเหตุการณ์ที่อาแจ็กซ์จะได้รับผลกระทบ
โปรดทราบว่า<f:view>
ไม่จำเป็นต้องไม่ซ้ำกันตลอดทั้งมุมมองและ / หรืออยู่ในแม่แบบหลักเท่านั้น นอกจากนี้ยังเป็นเรื่องถูกต้องตามกฎหมายที่จะ redeclare และซ้อนในเทมเพลตไคลเอ็นต์ มันเป็นพื้น "ขยาย" ผู้ปกครอง<f:view>
แล้ว เช่นในแม่แบบต้นแบบ:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
และในไคลเอนต์แม่แบบ:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
คุณสามารถห่อ<f:view>
ใน<c:if>
เพื่อให้เป็นแบบมีเงื่อนไข โปรดทราบว่ามันจะมีผลกับทั้งมุมมองไม่ใช่เฉพาะกับเนื้อหาที่ซ้อนอยู่เช่น<h:form>
ในตัวอย่างด้านบน
ดูสิ่งนี้ด้วย
ไม่เกี่ยวข้องกับปัญหาที่เป็นรูปธรรมการใช้ HTTP POST สำหรับการนำทางแบบหน้าต่อหน้าอย่างแท้จริงนั้นไม่ได้เป็นมิตรกับผู้ใช้ / SEO มากนัก ใน JSF 2.0 คุณควรจะชอบ<h:link>
หรือ<h:button>
เหนือสิ่ง<h:commandXxx>
อื่นสำหรับการนำทางหน้าวานิลลาธรรมดาต่อหน้า
ดังนั้นแทนที่จะเป็นเช่น
<h:form id="menu">
<h:commandLink value="Foo" action="foo?faces-redirect=true" />
<h:commandLink value="Bar" action="bar?faces-redirect=true" />
<h:commandLink value="Baz" action="baz?faces-redirect=true" />
</h:form>
ดีกว่าทำ
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
ดูสิ่งนี้ด้วย