问题
I'm having a performance problem with my JSF/RichFaces/Facelets ajax requests and from what I can tell its because the entire component tree is being rebuild on each ajax request. This is happening even if I use ajaxSingle=true, wrap sections in a4j:region, declare a single section for rerendering or none at all. Our page is a dynamic page with many nested levels. The page may contain around 800-900 fields (inputText, rich calendars, selectOneMenus, etc). The initial load time is an issue, but I understand that issue, its a lot of fields. Once we have that initial build/render time though we have designed all other actions to be ajax and only reRender what is needed. From facelets debug logs I see messages like this on any ajax call:
2011-08-24 22:19:03,054 DEBUG [facelets.viewhandler] (http-0.0.0.0-8080-2) Took
24445ms to build view: /oconsole/appfile.xhtml
2011-08-24 22:19:09,377 DEBUG [facelets.viewhandler] (http-0.0.0.0-8080-2) Took
6323ms to render view: /oconsole/appfile.xhtml
I'm not sure if something we are doing is causing the rebuild of the entire component tree, or facelets is determining this need required for some reason (stale cache?). Here is our stack: JBoss 5.1 JSF 1.2 RichFaces. 3.3.3.Final Facelets 1.1.15 Seam 2.1.2
I have tried adding some context parameters to see if they would help, but they did nothing: facelets.BUILD_BEFORE_RESTORE = false facelets.REFRESH_PERIOD = -1 or 5 (as in 5min)
Is there anyway to tell if our views are being cached properly? We do not delcare a state saving method, so I believe it defaults to server side. All our requests happen within seam long running conversations. I wasn'te sure if this plays a factor as I thought views get cached at session level? Any help would be greatly appreciated, thank you.
Update after more debugging:
The AjaxViewHandler (which has a member variable of the FaceletsViewHandler) has developmentMode=true set. I'm not sure if this is causing facelets to not cache any views so any changes would be refreshed during development cycles...?? Its been very difficult to find any information on facelets/JSF caching of views and the behavior and controlling that. Furthermore, when I add config param:
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>false</param-value>
</context-param>
This did not take! In debugger I still see true set. Since we have a lot of subviews I also tried com.sun.faces.numberOfLogicalViews and com.sun.faces.numberOfViewsInSession to 1000 up from 15(default) and this had no effect.
I also tried changing to client side state saving without any luck. Running out of ideas....hope someone can help....
It seems Seam 2.1 auto-initializes RichFaces and I'm not sure if that has something to do with it.....
回答1:
As with any performance problem, a profiler would help greatly in locating the bottlenecks. (Yes, you know it is the restore_view phase, but not where in the restore_view phase).
That said, the restore view phase indeed restores the entire view, not just the parts that will be processed or rendered. Quoting the RichFaces taglib documentation:
process: Id['s] (in format of call UIComponent.findComponent()) of components, processed at the phases 2-5 in case of AjaxRequest caused by this component. Can be single id, comma-separated list of Id's, or EL Expression with array or Collection
RESTORE_VIEW is phase 1. Also:
reRender: Id['s] (in format of call UIComponent.findComponent()) of components, rendered in case of AjaxRequest caused by this component. Can be single id, comma-separated list of Id's, or EL Expression with array or Collection
Moreover, I am not sure that UIComponent.findComponent() is implemented using a more suitable datastructure than a component tree. (Finding something in the component tree would boil down to linear search ...).
I have observed similar effects with JSF 2.0 (Mojarra). My conclusion was that views must not contain more than a couple dozen UIComponents, irrespective of whether they are rendered. (Put differently, AJAX is unsuitable for page navigation.) We indend to keep views small by including only components that are currently visible in the view, and switch views if many new components need to appear. That is, instead of one view with 10 tabs with 30 components each, we'd have 10 views, each only containing the content of a single tab. A drawback of that approach is that components are disposed when switching tabs, causing any state not held in backing beans to be lost.
I do not claim this to be a good solution. Alas, it was the best one I found when looking into this a couple weeks ago. I'd be happy to be shown a better one, too.
Edit
When I say restore, I mean ViewHandler.restoreView()
, which called both for an initial get request and a postback. It is incorrect to say that restoreView
would simply reuse an existing view as is. For instance, the JSF 2.0 spec mandates in section 7.6.2.7: ]
The
restoreView()
method must fulfill the following responsibilities:All implementations must:
- If no viewId could be identified, return
null
.- Call the
restoreView()
method of the associatedStateManager
, passing theFacesContext
instance for the current request and the calculated viewId, and return the returnedUIViewRoot
, which may benull
.
and in section 7.7.2:
JSF implementations support two primary mechanisms for saving state, based on the value of the javax.faces.STATE_SAVING_METHOD initialization parameter (see Section 11.1.3 “Application Configuration Parameters”). The possible values for this parameter give a general indication of the approach to be used, while allowing JSF implementations to innovate on the technical details:
- client -- [...]
- server -- Cause the saved state to be stored on the server in between requests. Implementations that wish to enable their saved state to fail over to a different container instance must keep this in mind when implementing their server side state saving strategy. The default implementation Serializes the view in both the client and server modes. In the server mode, this serialized view is stored in the session and a unique key to retrieve the view is sent down to the client. By storing the serialized view in the session, failover may happen using the usual mechanisms provided by the container.
Put differently, the AJAX support added to JSF (both the one added by RichFaces 3 to JSF 1.2, and the one incorporated into JSF 2.0) aims to reduce network bandwith consumption, not server side cpu consumption.
回答2:
From my analysis the issue is caused by the implementation of facelets. Based on debugger the following lines of FaceletViewHandler
class, causes rebuild of tree on each (even AJAX) request (buildBeforeRestore
is false, so buildView
method is invoked):
// build view - but not if we're in "buildBeforeRestore"
// land and we've already got a populated view. Note
// that this optimizations breaks if there's a "c:if" in
// the page that toggles as a result of request processing -
// should that be handled? Or
// is this optimization simply so minor that it should just
// be trimmed altogether?
if (!this.buildBeforeRestore
|| viewToRender.getChildren().isEmpty()) {
this.buildView(context, viewToRender);
}
So in my mind to resolve the issue of tree rebuild on each request is to go deeply to facelets implementation and do reimplementation... I would rather prefer restructure the view and minimize number of components, so build tree time is low.
来源:https://stackoverflow.com/questions/7190225/jsf-view-getting-rebuild-on-each-ajax-request