为什么JSF转换器是在任何其他托管bean之前创建的



JSF Converter似乎是在xhtml页面上的任何其他托管bean之前调用的。它甚至是用rendered=falseh:selectOneMenu组件上创建的。

我创建了3个托管bean来测试初始化序列,托管bean按顺序初始化,它们出现在xhtml中,但在它们之前,JSF转换器是创建的,尽管它是最后一个。

表单

<h:form>
    <h:inputText value="#{creationTime1.string1}"/>
    <h:inputText value="#{creationTime3.string3}"/>
    <h:inputText value="#{creationTime2.string2}"/>
    <h:selectOneMenu value="#{creationTime1.competitor}" rendered="false" converter="#{testConverter}" >
        <f:selectItem itemValue="#{null}" itemLabel="#{msg.none}" />
        <f:selectItems value="#{creationTime1.competitorList}" var="competitor" itemValue="#{competitor}"
                       itemLabel="#{competitor.idCompetitor}"/>
    </h:selectOneMenu>
    <h:commandButton value="GO" action="#{creationTime1.submit}"/>
</h:form>

转换器

@Named(value = "testConverter")
@ViewScoped
public class TestConverter implements Converter, Serializable {
    @PostConstruct
    private void init() {
        System.out.println(System.currentTimeMillis() +  " || TestConverter init");
    }
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        ...
    }
    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        ...
    }
}

托管Bean(所有名称相同但不同)

@Named(value = "creationTime2")
@ViewScoped
public class CreationTime2 implements Serializable {
    private String string2;
    public String getString2() {
        System.out.println("getter srting2");
        return string2;
    }
    public void setString2(String string2) {
        this.string2 = string2;
    }
    @PostConstruct
    private void init() {
        System.out.println(System.currentTimeMillis() +  " || CreationTime2 init");
    }
}

页面访问结果

Info:   START PHASE RESTORE_VIEW 1
Info:   END PHASE RESTORE_VIEW 1
Info:   START PHASE RENDER_RESPONSE 6
Info:   1451147920374 || TestConverter init
Info:   1451147920401 || CreationTime1 init
Info:   getter string1
Info:   1451147920407 || CreationTime3 init
Info:   getter string3
Info:   1451147920414 || CreationTime2 init
Info:   getter string2
Info:   END PHASE RENDER_RESPONSE 6

我还尝试了使用@FacesConverter而不是CDIBean,结果是相同的。

  1. 为什么转换器在任何其他bean之前被调用
  2. 为什么使用rendered=false创建转换器?难道它不应该像不存在一样通过h:celectOneMenu而不创建自己吗
  3. 我能以某种方式让转换器在这3个bean之后创建自己吗(按照它在xhtml页面上出现的顺序)

使用

  • Mojarra 2.2.7
  • GlassFish 4.1

为什么转换器在任何其他bean之前被调用

转换器并不完全是";调用";即getAsObject()getAsString()在该时刻都没有被调用。它只是在构建视图的过程中被实例化,然后被指定为父ValueHolder组件的属性。JSF对组件上声明的每个转换器、验证器和(ajax)行为都执行此操作。

转换器实际上是一个托管bean,这并没有什么区别。这只是一个技巧,以便能够在其中注入a.o.EJB。JSF检查该值是文本字符串还是EL表达式。如果是文字字符串,则将其视为创建转换器实例的转换器ID,否则,如果EL表达式返回一个具体的Converter实例,则直接使用它。或者,如果什么都没有,那么JSF将通过匹配value类型(如果有的话)的目标类来创建转换器。


为什么使用rendered=false创建转换器

因为rendered属性仅在视图渲染时计算,而不是在视图构建时计算。


我能以某种方式让转换器在这3个bean之后创建自己吗(按照它在xhtml页面上的显示顺序)

否,但是如果";不必要的";转换器的实例化是您关心的问题,然后您可以使用JSTL来有条件地添加转换器。

<h:selectOneMenu ... rendered="#{bean.condition}">
    <c:if test="#{bean.condition}"><f:converter binding="#{testConverter}" /></c:if>
    ...
</h:selectOneMenu>

您只需要记住,<x:someComponent rendered><c:if test>不会同时进行评估。<c:if test>在视图构建时间期间评估,<x:someComponent rendered>在视图渲染时间期间评估。因此,如果由于某种原因,条件在这些时刻之间发生了变化,那么您基本上需要重建视图(即显式导航到同一视图,而不是返回void/null)。如果在重新创建bean时将条件绑定到视图范围的bean属性,那么这可能会变得很糟糕。您基本上需要在@PostConstruct期间保留该条件。

尽管如此,无论如何实例化的转换器(以及验证器和行为)应该是您最不关心的问题。然而,如果在实例化方面存在性能问题,或者在实例化排序方面存在技术问题,那么您最好根据具体的功能需求寻找不同的解决方案。

另请参阅:

  • JSF2 Facelets中的JSTL。。。有道理吗

最新更新