我正在开发一个JSF自定义组件 . 该组件的目的是封装另一个组件(即PrimeFaces表)并向其添加自定义行为 . 例如,它支持的功能之一是从底层数据或某些属性动态创建PrimeFaces列 . 此外,它支持在XHTML中声明其他PrimeFaces列,这些列也应添加到封装的PrimeFaces表中 .
考虑这个例子:
<my:table id="table" fields="title,label,value,additional">
<primefaces:column id="additional">
some content
</primefaces:column>
</my:table>
我的自定义组件在渲染过程中从 fields
属性动态创建PrimeFaces列 . 然后它将所有 column
子项移动到PrimeFaces表,因此在呈现组件树之后如下所示:
my:table id="table"
|---primefaces:table id="table_table"
|---primefaces:column id="title"
|---primefaces:column id="label"
|---primefaces:column id="value"
|---primefaces:column id="additional"
在第一次渲染期间,这很好 . 但是,当我然后执行我的组件的AJAX更新时,我得到以下异常:
javax.faces.FacesException: Cannot remove the same component twice: table:additional
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.handleAddRemoveWithAutoPrune(StateContext.java:761)
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.handleRemove(StateContext.java:629)
at com.sun.faces.context.StateContext$AddRemoveListener.processEvent(StateContext.java:342)
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.processEvent(StateContext.java:565)
at javax.faces.event.SystemEvent.processListener(SystemEvent.java:108)
at javax.faces.event.ComponentSystemEvent.processListener(ComponentSystemEvent.java:118)
at com.sun.faces.application.ApplicationImpl.processListenersAccountingForAdds(ApplicationImpl.java:2218)
at com.sun.faces.application.ApplicationImpl.invokeViewListenersFor(ApplicationImpl.java:2036)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:290)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:245)
at javax.faces.application.ApplicationWrapper.publishEvent(ApplicationWrapper.java:726)
at javax.faces.component.UIComponentBase.disconnectFromView(UIComponentBase.java:2275)
at javax.faces.component.UIComponentBase.doPreRemoveProcessing(UIComponentBase.java:1939)
at javax.faces.component.UIComponentBase.setParent(UIComponentBase.java:437)
at javax.faces.component.UIComponentBase$ChildrenList.remove(UIComponentBase.java:2757)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.adjustIndexOfDynamicChildren(ComponentTagHandlerDelegateImpl.java:283)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:223)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.DefineHandler.applyDefinition(DefineHandler.java:106)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:206)
at com.sun.faces.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:395)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:366)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:111)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:194)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.IncludeHandler.apply(IncludeHandler.java:124)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:116)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.DefineHandler.applyDefinition(DefineHandler.java:106)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:206)
at com.sun.faces.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:395)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:366)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:111)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.jsf.core.ViewHandler.apply(ViewHandler.java:225)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:174)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:174)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:161)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.buildView(FaceletViewHandlingStrategy.java:1006)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:99)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at de.gebit.trend.servlet.security.AuthorizationFilter.doFilter(AuthorizationFilter.java:269)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:789)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
因此,只有从我的表移动到PrimeFaces表的 column
才会产生此错误 . 其他列不会重新创建,因为在渲染过程中,我使用存储在_2595126中的实例变量来指示已经创建了列 .
我以某种方式理解这个异常的来源,并且它与JSF保存完整的组件树有关,当JSF恢复视图时,保存的状态与XHTML不一致 . 我不知道的是,如何解决这个问题 .
有人可以向我解释一下这种状态保存机制是如何工作的,特别是与动态添加的子节点相结合以及如何避免这种异常?
更新(10.02.2017)
我创建了一个小型示例项目,没有其他之前使用过的框架 . 它可以在我的GitHub profile上找到 . 之前使用的一个主要框架是在 StateContext
中安装的 AddRemoveListeners
用于重放动态操作 . 为了避免这对我的问题产生影响并创建可重现的环境,我删除了它们 .
我现在看到的行为略有不同(不再有例外),取决于 partial state saving 是否启用/禁用以及我用来移动 primefaces:column
的方法:
在所有情况下,表的第一次渲染工作正常 . 然后,我通过提交分页请求来执行回发请求 . 在某些情况下,这种行为是错误的 .
启用了部分状态保存
启用部分状态保存后,分页不起作用 . 我没有例外,但有很多类似的警告:
Feb 10, 2017 4:33:11 PM com.sun.faces.application.view.FaceletPartialStateManagementStrategy saveDynamicActions
WARNUNG: Unable to save dynamic action with clientId 'form:table:table_table:additional' because the UIComponent cannot be found
对于在 primefaces:table
中动态创建或移动到的每个组件都会出现此警告,或者是其中一个组件的子组件 .
禁用部分状态保存
禁用部分状态保存后,分页可以正常工作,但显示不同的行为,具体取决于移动一个自定义 primefaces:column
的时间 .
在渲染响应阶段移动column
在渲染响应阶段移动 primefaces:column
时,例如在 encodeXxx
,一切正常 . 所有列都按正确的顺序排列,并且正确的值和分页工作完全正常 .
使用PostAddToViewEvent
移动column
使用此方法时,根据 @BalusC 建议,移动的 primefaces:column
在分页时消失 . 多次调用 PostAddToViewEvent
并在处理此事件时移动 column
,但是在渲染时,它已经消失,只有之前创建的三个 column
仍然存在 .
在这一点上,我不仅仅是困惑 . 这是Mojarra或Primefaces中的错误还是我做错了什么? JSF甚至可以实现这种行为吗?
2 回答
JSF状态管理记住对组件树的动态操作,以便它可以确保它在回发后的恢复视图之后,就像在生成帖子形式的上一个请求的呈现响应期间那样 .
你得到的例外,
基本上告诉你已经将同一个组件两次添加到同一个父组件中 .
换句话说,您从组件树中获取了所需的组件,然后将其添加到所需的父组件中 . 但是,根据给定的例外情况,当时 already 附加到所需的父级!实际上,你执行了无操作 . 但JSF会记住每个动态组件的添加/删除同一视图上的回发,即使它实际上是一个无操作 . 那部分可能反过来又是JSF实现本身的一个错误,但是你应该首先在组件处于理想的位置时不要移动组件 .
快速解决方法是通过
UIComponent#getParent()
检查组件的父级,如果它不是所需的那个,如果是,则跳过getChildren().add()
调用 .一个hack就是将UIComponent#setInView()设置为
false
,以便JSF不记得动态动作 .但是在使用此方法时要谨慎,另请参阅javadoc .
但是,执行组件树操作的最自然方式是
postAddtoViewEvent
侦听器,而不是encodeXxx()
方法 .简单的答案是:它正在使用的是其他框架故障 . 该框架覆盖了JSF的状态保存机制,并用自定义机制替换它 . 但是,实现是错误的,并没有处理负责正确保存动态组件的动作的
DynamicAddRemoveListener
. 他们修复了这个bug,现在它运行正常 .但是,我想指出修复我的组件需要做的几件事:
首先, @BalusC 向我指出了在自定义JSF组件中移动子组件的正确方法:它应该使用
PostAddToView
事件监听器来完成 .@覆盖
public void processEvent(ComponentSystemEvent event){
if(事件instanceof PostAddToViewEvent){
targetParent.getChildren()添加(componentToMove) .
}
}
}
然而,这种方法的缺点是,此时不会设置组件属性 . 因此,如果您需要这些组件,则只能在渲染响应阶段创建/移动组件 .
其次,自定义JSF组件的子组件不应保存在
StateHelper
中 . 应该在每个请求上重新创建它们,以便JSF在重放动态操作时找到这些组件 .第三,在创建组件时,应始终设置动态创建的子组件(如果已设置)的ID . 我的自定义组件仅在呈现响应阶段设置其子组件的ID,因此当JSF尝试重放动态操作时,它无法找到相应的组件 . 这是上面 Partial state saving enabled 部分提到的问题的解决方案 .
因此,通过所有这些适应性和其他框架的修复,现在最终我的组件按照需要工作 .