Giới thiệu
Các ViewExpiredException
sẽ được ném bất cứ khi nào javax.faces.STATE_SAVING_METHOD
được cài đặt để server
(mặc định) và enduser gửi một yêu cầu HTTP POST trên một cái nhìn qua <h:form>
với <h:commandLink>
, <h:commandButton>
hoặc <f:ajax>
, trong khi tình trạng xem liên quan là không có sẵn trong phiên giao dịch nữa.
Trạng thái xem được xác định là giá trị của trường đầu vào ẩn javax.faces.ViewState
của <h:form>
. Với phương thức lưu trạng thái được đặt thành server
, điều này chỉ chứa ID trạng thái xem tham chiếu trạng thái xem được tuần tự hóa trong phiên. Vì vậy, khi phiên hết hạn vì một số lý do (đã hết thời gian ở phía máy chủ hoặc máy khách hoặc cookie phiên không được duy trì nữa vì một số lý do trong trình duyệt hoặc do gọi HttpSession#invalidate()
máy chủ hoặc do lỗi cụ thể của máy chủ với cookie phiên như được biết đến trong WildFly ), sau đó trạng thái xem được tuần tự hóa không còn khả dụng trong phiên và trình kết thúc sẽ có ngoại lệ này. Để hiểu hoạt động của phiên, xem thêm Làm thế nào để các servlet hoạt động? Khởi tạo, phiên, biến chia sẻ và đa luồng .
Ngoài ra còn có giới hạn về số lượt xem mà JSF sẽ lưu trữ trong phiên. Khi giới hạn được nhấn, thì chế độ xem ít được sử dụng gần đây nhất sẽ hết hạn. Xem thêm com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .
Với phương thức lưu trạng thái được đặt thành client
, trường javax.faces.ViewState
đầu vào ẩn chứa thay vào đó là toàn bộ trạng thái xem được tuần tự hóa, vì vậy bộ xử lý sẽ không nhận được ViewExpiredException
khi hết phiên. Tuy nhiên, điều đó vẫn có thể xảy ra trên môi trường cụm ("LRI: MAC không xác minh" là có triệu chứng) và / hoặc khi có thời gian chờ cụ thể thực hiện ở trạng thái phía máy khách được định cấu hình và / hoặc khi máy chủ tạo lại khóa AES trong khi khởi động lại , xem thêm Bắt ViewExpiredException trong môi trường cụm trong khi phương thức lưu trạng thái được đặt thành phiên khách và phiên người dùng là hợp lệ làm thế nào để giải quyết nó.
Bất kể giải pháp nào, hãy đảm bảo bạn không sử dụng enableRestoreView11Compatibility
. nó hoàn toàn không khôi phục trạng thái xem ban đầu. Về cơ bản, nó tái tạo lại khung nhìn và tất cả các hạt đậu được xem có liên quan từ đầu và do đó làm mất tất cả dữ liệu gốc (trạng thái). Vì ứng dụng sẽ hoạt động theo cách khó hiểu ("Này, giá trị đầu vào của tôi ở đâu .. ??"), điều này rất tệ cho trải nghiệm người dùng. Tốt hơn nên sử dụng các chế độ xem không trạng thái hoặc <o:enableRestorableView>
thay vào đó để bạn có thể quản lý nó trên một chế độ xem cụ thể thay vì trên tất cả các chế độ xem.
Về lý do tại sao JSF cần lưu trạng thái xem, hãy đi đến câu trả lời này: Tại sao JSF lưu trạng thái của các thành phần UI trên máy chủ?
Tránh ViewExpiredException trên điều hướng trang
Để tránh ViewExpiredException
khi ví dụ: điều hướng trở lại sau khi đăng xuất khi lưu trạng thái được đặt thành server
, chỉ chuyển hướng yêu cầu POST sau khi đăng xuất là không đủ. Bạn cũng cần hướng dẫn trình duyệt không lưu các trang JSF động, nếu không trình duyệt có thể hiển thị chúng từ bộ đệm thay vì yêu cầu một trang mới từ máy chủ khi bạn gửi yêu cầu GET trên đó (ví dụ bằng nút quay lại).
Trường javax.faces.ViewState
ẩn của trang được lưu trong bộ nhớ cache có thể chứa giá trị ID trạng thái xem không còn hợp lệ trong phiên hiện tại. Nếu bạn (ab) sử dụng POST (liên kết lệnh / nút) thay vì GET (liên kết / nút thông thường) để điều hướng từ trang này sang trang khác và nhấp vào nút / liên kết lệnh như vậy trên trang được lưu trong bộ nhớ cache, thì điều này sẽ lần lượt thất bại với a ViewExpiredException
.
Để kích hoạt chuyển hướng sau khi logout trong JSF 2.0, hoặc thêm <redirect />
vào <navigation-case>
trong câu hỏi (nếu có), hoặc thêm ?faces-redirect=true
vào outcome
giá trị.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
hoặc là
public String logout() {
// ...
return "index?faces-redirect=true";
}
Để hướng dẫn trình duyệt không lưu trữ các trang JSF động, hãy tạo một trang Filter
được ánh xạ trên tên servlet của FacesServlet
và thêm các tiêu đề phản hồi cần thiết để vô hiệu hóa bộ đệm của trình duyệt. Ví dụ
@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);
}
// ...
}
Tránh ViewExpiredException khi làm mới trang
Để tránh ViewExpiredException
khi làm mới trang hiện tại khi lưu trạng thái được đặt thành server
, bạn không chỉ cần đảm bảo rằng bạn đang thực hiện điều hướng từ trang này sang trang khác bằng GET (liên kết / nút thông thường), mà bạn cũng cần đảm bảo rằng bạn đang sử dụng ajax để gửi biểu mẫu. Nếu bạn đang gửi biểu mẫu một cách đồng bộ (không phải ajax), thì tốt nhất bạn nên đặt chế độ xem không trạng thái (xem phần sau) hoặc gửi chuyển hướng sau POST (xem phần trước).
Có một ViewExpiredException
làm mới trên trang là trong cấu hình mặc định một trường hợp rất hiếm. Nó chỉ có thể xảy ra khi giới hạn về số lượt xem mà JSF sẽ lưu trữ trong phiên được nhấn. Vì vậy, điều đó sẽ chỉ xảy ra khi bạn đặt thủ công mức giới hạn quá thấp hoặc bạn liên tục tạo chế độ xem mới trong "nền" (ví dụ: cuộc thăm dò ajax được triển khai kém trong cùng một trang hoặc do 404 thực hiện kém trang lỗi trên hình ảnh bị hỏng của cùng một trang). Xem thêm com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews để biết chi tiết về giới hạn đó. Một nguyên nhân khác là có các thư viện JSF trùng lặp trong đường dẫn lớp chạy xung đột lẫn nhau. Quy trình đúng để cài đặt JSF được nêu trong trang wiki JSF của chúng tôi .
Xử lý ViewExpiredException
Khi bạn muốn xử lý một điều không thể tránh khỏi ViewExpiredException
sau một hành động POST trên một trang tùy ý đã được mở trong một số tab / cửa sổ trình duyệt trong khi bạn đăng xuất trong một tab / cửa sổ khác, thì bạn muốn chỉ định một trang error-page
cho web.xml
nó. đến trang "Phiên của bạn đã hết giờ". Ví dụ
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
Sử dụng nếu cần một tiêu đề làm mới meta trong trang lỗi trong trường hợp bạn có ý định thực sự chuyển hướng xa hơn đến trang chủ hoặc trang đăng nhập.
<!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
in content
đại diện cho số giây trước khi chuyển hướng, 0
do đó có nghĩa là "chuyển hướng ngay lập tức", bạn có thể sử dụng ví dụ 3
để cho trình duyệt chờ 3 giây với chuyển hướng)
Lưu ý rằng việc xử lý các ngoại lệ trong các yêu cầu ajax yêu cầu đặc biệt ExceptionHandler
. Xem thêm Thời gian chờ phiên và xử lý ViewExpiredException trên yêu cầu ajax của JSF / PrimeFaces . Bạn có thể tìm thấy một ví dụ trực tiếp tại trang giới thiệu OmniFacesFullAjaxExceptionHandler
(điều này cũng bao gồm các yêu cầu không phải là ajax).
Cũng lưu ý rằng "chung" trang báo lỗi của bạn nên được ánh xạ vào <error-code>
trong 500
thay vì một <exception-type>
số ví dụ java.lang.Exception
hoặc java.lang.Throwable
, nếu không tất cả trường hợp ngoại lệ được bọc trong ServletException
như ViewExpiredException
vẫn sẽ kết thúc trong trang lỗi chung. Xem thêm ViewExpiredException được hiển thị trong java.lang.Thẻ lỗi trang trong tệp web.xml .
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Quan điểm không quốc tịch
Một cách khác hoàn toàn khác là chạy các khung nhìn JSF ở chế độ không trạng thái. Bằng cách này, không có trạng thái nào của JSF sẽ được lưu và các khung nhìn sẽ không bao giờ hết hạn, mà chỉ được xây dựng lại từ đầu theo mọi yêu cầu. Bạn có thể bật quan điểm không quốc tịch bằng cách thiết lập transient
thuộc tính của <f:view>
để true
:
<f:view transient="true">
</f:view>
Bằng cách này, javax.faces.ViewState
trường ẩn sẽ nhận được giá trị cố định "stateless"
trong Mojarra (chưa kiểm tra MyFaces tại thời điểm này). Lưu ý rằng tính năng này đã được giới thiệu trong Mojarra 2.1.19 và 2.2.0 và không có sẵn trong các phiên bản cũ hơn.
Hậu quả là bạn không thể sử dụng xem phạm vi đậu nữa. Bây giờ họ sẽ hành xử như yêu cầu đậu có phạm vi. Một trong những nhược điểm là bạn phải tự theo dõi trạng thái bằng cách thay đổi các đầu vào ẩn và / hoặc các tham số yêu cầu lỏng lẻo. Chủ yếu là những hình thức với các lĩnh vực đầu vào với rendered
, readonly
hoặc disabled
thuộc tính được điều khiển bởi sự kiện ajax sẽ bị ảnh hưởng.
Lưu ý rằng <f:view>
không nhất thiết phải là duy nhất trong toàn bộ chế độ xem và / hoặc chỉ nằm trong mẫu chính. Nó cũng hoàn toàn hợp pháp để tái phân phối và lồng nó vào một máy khách mẫu. Về cơ bản nó "kéo dài" cha mẹ <f:view>
rồi. Ví dụ: trong mẫu chính:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
và trong ứng dụng khách mẫu:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Bạn thậm chí có thể bọc <f:view>
trong một <c:if>
để làm cho nó có điều kiện. Lưu ý rằng nó sẽ áp dụng trên toàn bộ khung nhìn, không chỉ trên các nội dung lồng nhau, chẳng hạn như <h:form>
trong ví dụ trên.
Xem thêm
Không liên quan đến vấn đề cụ thể, sử dụng HTTP POST để điều hướng từ trang này sang trang khác không thân thiện với người dùng / SEO. Trong JSF 2.0, bạn thực sự nên thích <h:link>
hoặc <h:button>
hơn <h:commandXxx>
những người điều hướng trang vanilla đơn giản.
Vì vậy, thay vì ví dụ
<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>
làm tốt hơn
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Xem thêm