关于JSF规范中UIComponent.invokeOnComponent()方法行为的技术问询
你遇到的这个问题其实戳中了JSF规范里一个容易被忽略的细节——UIComponent.invokeOnComponent()在遍历子组件时的上下文维护逻辑。咱们来一步步拆解分析:
问题概述
我在嵌套使用两个带有独立后台组件的复合组件时遇到了异常行为:invokeOnComponent()方法在遍历子组件过程中并未调用pushComponentToEL(),但ImplicitELResolver依赖UIComponent.getCurrentCompositeComponent()来解析#{cc.xx}这类属性值。这就引发了疑问:invokeOnComponent()在遍历子组件前,是否应该像调用回调前那样执行pushComponentToEL()?我已经在后台Bean重写invokeOnComponent()方法时手动实现了这个逻辑作为临时解决方案,但不确定这么做会不会带来其他未知问题。
详细场景
主页面代码
<html> <compositeComponent:comp1> <compositeComponent:comp2> </html>
复合组件comp1代码
<composite:interface componentType="comp1"> </composite:interface> <composite:implementation> <myComponentLib:myComponent /> </composite:implementation>
对应后台组件BACKING_COMPONENT_1。
复合组件comp2代码
<composite:interface componentType="comp2"> </composite:interface> <composite:implementation> <p:dataTable value="#{cc.myList}" > </p:dataTable> </composite:implementation>
对应后台组件BACKING_COMPONENT_2。
异常触发流程
- 调用复合组件1的
encodeAll方法 - 执行复合组件1的
encodeBegin,调用pushComponentToEL(),更新CURRENT_COMPOSITE_COMPONENT_STACK - 调用
myComponent的encodeAll,其渲染器需要查找组件,进而调用UIViewRoot的invokeOnComponent() - 递归调用到复合组件2内DataTable的
invokeOnComponent()方法 - DataTable需要解析
#{cc.myList},ImplicitELResolver通过UIComponent.getCurrentCompositeComponent()获取基类,但此时依赖的CURRENT_COMPOSITE_COMPONENT_STACK返回的是BACKING_COMPONENT_1而非预期的BACKING_COMPONENT_2
问题分析
从JSF规范的定义来看,这里确实存在一个模糊地带:规范仅明确了invokeOnComponent()在找到目标clientId并调用回调前需要执行pushComponentToEL()和popComponentToEL(),但并未规定在遍历facetsAndChildren子组件的过程中是否需要同样执行这对操作。
而你的场景恰恰暴露了这个漏洞:当嵌套复合组件时,遍历子组件的过程中如果不维护CURRENT_COMPOSITE_COMPONENT_STACK,后续子组件的EL解析就会错误地继承父复合组件的上下文,导致#{cc}指向了错误的后台组件。
疑问解答
1. 遍历facetsAndChildren时调用invokeOnComponent是否应使用push/pop机制?
答案是肯定的。从JSF的EL上下文设计逻辑来看,CURRENT_COMPOSITE_COMPONENT_STACK是用来跟踪当前EL解析上下文所属的复合组件的,任何进入子复合组件的操作都应该更新这个栈,离开时恢复。invokeOnComponent()在遍历子组件时,本质上是进入了子组件的上下文,所以必须调用pushComponentToEL()来更新栈,否则后续的#{cc}解析必然出错。
2. 这个workaround是否会引发其他未知问题?
只要你的实现严格遵循push/pop的成对调用原则,一般不会有问题:
- 确保在调用子组件的
invokeOnComponent()前调用pushComponentToEL() - 务必用
try/finally块保证在子组件调用完成后(无论成功还是失败)调用popComponentToEL(),避免栈溢出或上下文混乱 - 不要破坏原有
invokeOnComponent()的核心逻辑(比如找到目标组件后的回调执行逻辑)
另外,建议你检查下使用的JSF实现(比如Mojarra、MyFaces)的最新版本,部分厂商可能已经针对这个规范模糊点补全了实现逻辑,如果升级版本能解决问题,就不需要自己维护这个workaround了。
内容的提问来源于stack exchange,提问作者xqvier

