Uploaded image for project: 'MyFaces Core'
  1. MyFaces Core
  2. MYFACES-3825

[perf] Cache EL expressions using an indirection for ui:param and user tag attributes

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Closed
    • Major
    • Resolution: Fixed
    • None
    • 2.2.0
    • JSR-344
    • None

    Description

      I have been trying for some time to find new ways to improve the code inside
      MyFaces. Working in MYFACES-3811 (fix c:forEach) I have realized that the way
      how VariableMapper works allows us to cache EL expressions in those places where
      we have thought EL caching was not possible.

      This fact is important because it changes the way how we have been thinking around
      view pooling technique (See MYFACES-3664 for details). If all
      ValueExpression/MethodExpression instances in a view can be considered "static" or
      in other words it does not change each time the view is built or refreshed, we can
      be sure that with a plain visitTree call it is possible to "reset" any view and
      reuse it safely, even in cases like when ui:param is used or user facelet tags.
      If all components in a view support pooling (hard/soft reset using saveState
      method), any view using those components can be poolable.

      First of all, let's remember how VariableMapper works. Basically it is just a map
      with var names as keys and ValueExpression as values. When a EL expression is
      created, the variables that are on the context VariableMapper and are used
      to solve the expression are copied and stored into an inner VariableMapper of
      the created EL expression. For example if we have this:

      <c:set var="item" value="Hello"/>
      <c:set var="item2" value="#

      {item}"/>
      <c:set var="item3" value="#{item2}"/>

      the EL expression for item2 will have an inner VariableMapper with an EL
      expression pointing to "Hello".

      Now, we need to remember the problematic cases for EL caching:

      1. Use combinations of c:set and c:if

      <c:if test="#{condition}">
      <c:set var="item" value="Hello"/>
      <c:if>
      <h:outputText value="#{item}

      "/>

      This case is unlikely, but most of all, it can be refactored very easily to avoid
      the c:if and move the condition to the c:set EL Expression. It is common to found
      this technique in old JSP pages. But it is clear with JSF, this kind of logic
      should reside in a managed bean. So at the end it is not a big deal. Anyway,
      There is a mode called "strict" that disable EL caching for the whole page if
      c:set is found.

      2. Use of ui:param

      <ui:decorate template="uiparamcache1_1.xhtml">
      </ui:decorate>
      <ui:decorate template="uiparamcache1_1.xhtml">
      <ui:param name="param1" value="ALFA"/>
      </ui:decorate>

      The first time the template is called, it has no params, so all expressions are
      cached inside the inner template. But once we call the same template again, those
      cached expressions are now invalid and needs to be recalculated again. The hack
      done with "alwaysRecompile" mode recompiles the facelet, but takes into account
      the known parameters for the template. In this way, the EL expressions that are
      affected by the param are not cached.

      3. Use of facelet user tags

      <user:usertagtest1 var1="ALFA" id="comp1">
      </user:usertagtest1>
      <user:usertagtest1 var2="BETA" id="comp2">
      </user:usertagtest1>
      <user:usertagtest1 var1="GAMMA" var2="OMEGA" id="comp3">
      </user:usertagtest1>

      This is quite the same to the case with ui:param, but in this case affect facelet tag
      attributes.

      4. An expression uses a variable resolved through VariableMapper

      This is unlikely, because there are no standard tags using this strategy, but it is
      possible to create a facelet tag that uses a VariableMapper wrapper. This is not
      something we should worry about.

      In MYFACES-3811 (fix c:forEach), there is a part where a wrapper
      (IteratedValueExpression or MappedValueExpression) is required to hold
      the associated item and inject it into the VariableMapper. This is indeed a good idea,
      because it shows that we can just put a wrapper inside VariableMapper and things will
      keep working.

      If we can substitute the ValueExpression associated with a var with something else, we
      can avoid the propagation effect that makes EL caching fail in 2 and 3. The trick is
      use an unique id associated with the facelet tag and put the real EL expression in
      a central point like FaceletState object, which is stored in UIViewRoot. The resulting
      structure can be generated over and over if PSS is enabled and if is disable, it needs
      to be saved with the state. If the component tree changes dynamically, the generated
      structure will change too.

      The final effect will be that 100% of the EL Expressions managed by facelets using
      "alwaysRecompile" mode will be cacheable, which will be a great improvement. It also
      removes one of the biggest disadvantages we had for include view pooling technique
      into MyFaces 2.2.x.

      Attachments

        Activity

          People

            lu4242 Leonardo Uribe
            lu4242 Leonardo Uribe
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: