4 Replies Latest reply on Dec 18, 2012 11:37 PM by yue.neil

    How-to: avoid method or getter to be called several times by caching result

    jkronegg

      In a .xhtml page, JSF may call your method or getters several times. This may cause performance issues if the method/getter takes a lot of time to complete (e.g. isRendered(), see JBSEAM-3008). Another problem is that the returned result can change between calls, leading to incoherences in the JSF page.


      While the forum provide some kind of solution, the provided solutions are not fully described and compared. This post aims to correct that fact. Feel free to comment and maybe add other methods.


      I will first compare the methods on overall, then describe each technique individually.


      Overall comparison



      The table below presents the caching methods. The Result computation column can contain the following values:



      • call: the result is computed every call

      • creation : the result is computed at the component creation time

      • lazy : the result is computed at the first call




       
         
         
         
         
         
         
       
       
         
         
         
         


       
       
         
         
         
         


       
       
         
         
         
         
         
         
       
       
         
         
         
         
         
         
       
      Method nameDescriptionResult computationAdvantagesDisadvantagesReference
      DefaultDo nothing and let the Seam framework call methods when requiredcall


      • Simplicity






      • Performance issues (the getter is called several times for the same JSF page)




      -
      @Create/@FactoryAnnotate the property initializer method and it will be called after the instanciation+injection processcreation


      • Initializer contains only business code






      • Initializer can not depend on data that are not present at creation time

      • Requires one property + getter + initialization method

      • Cache scope is the same as the component




      Method called several times
      In-method cacheCache the result in the method itself by using a if (x==null) {x=...} guardlazy


      • Great control over custom caching strategies

      • No initializer



         



      • Getter method contains non-business code (related to the caller)

      • Requires to have a property dedicated to the cached result

      • Cache scope is the same as the component



         
      Why does JSF call my getter hundreds of times?
         
      Interceptor cache
         
      Build a Seam Interceptor which will be used to cache the value.lazy


      • Getter method contains only business code

      • Cache scope can be finer than the component scope

      • No initializer



         



      • Perturbate logging/profiling (because the method is called only once)



         
      -
         


      Default method



      The default method is used to define the following baseline code:


      @Name("defaultExample")
      @Scope(ScopeType.CONVERSATION)
      public class DefaultExampleAction implements DefaultExample {
      
        public Person getPerson() {
          Person person = ... // e,g. database lookup
          return person;
        }
      
      }
      



      When using such code, the database request will be done every time the getPerson() method is called, which is often the case in a JSF page.


      @Create and @Factory method



      This method is described in the Method called several times post. The @Factory annotation is used for injected components, while the @Create is used for standard Java properties.
      The typical code for the @Factory annotation is the following :


      @Name("factoryExample")
      @Scope(ScopeType.CONVERSATION)
      public class FactoryExampleAction implements FactoryExample {
      
        @In
        private Person person;
      
        public Person getPerson() { return person; }
      
        @Factory("person")
        public void personInitializer() {
          person = ... // e,g. database lookup
        }
      }
      



      The typical code for the @Create annotation is the following :


      @Name("createExample")
      @Scope(ScopeType.CONVERSATION)
      public class CreateExampleAction implements CreateExample {
      
        private Person person;
      
        public Person getPerson() { return person; }
      
        @Create
        public void personInitializer() {
          person = ... // e,g. database lookup
        }
      }
      



      For both annotations, the Seam component life cycle is the following:



      1. component instanciation

      2. injection and calls to @Factory annotated methods to create the injected components

      3. call of @Create annotated methods

      4. JSF calls the getter getPerson() several times



      These annotations have the advantage to be called at the component creation time (this ensures that all the required fields are initialized when the component is really used). On the other side, the component instanciation duration will be longer, the property initializer method cannot use dynamic data not available at creation time.


      As a side comment, some programmers complains about @Factory or @Create annotations are not working on the following code (see example in this post):


      @Name("createBadExample")
      @Scope(ScopeType.CONVERSATION)
      public class CreateBadExampleAction implements CreateExample {
      
        private Person person;
      
        @Create // called once at creation and at every call => bad => annotate the initializer, not the getter!
        public Person getPerson() {
          person = ... // e,g. database lookup
          return person;
        }
      
      }
      



      It should remembered that the @Factory or @Create annotations must be added on the initializer method and not on the getter itself!



      In-method cache method



      This method is described in the Seam FAQ :


      @Name("inMethodCacheExample")
      @Scope(ScopeType.CONVERSATION)
      public class InMethodCacheExampleAction implements InMethodCacheExample {
      
        private Person person = null;
      
        public Person getPerson() {
          if (person==null) {
            // not yet cached => cache it
            person = ... // e,g. database lookup
          }
          return person;
        }
      
      }
      



      The getPerson() method now uses lazy-loading which improves the component instanciation performance. The disdvantage is that it requires the person property and that the cache scope is the same as the component. Moreover, the getter now contains code which is related to the caller habits and not to the business job of getting the Person (I do not think this is the general Seam philosophy).


      A variant can be to use the Seam page/session/etc. context, so the code can be the following:


      @Name("inMethodCacheExample")
      @Scope(ScopeType.CONVERSATION)
      public class InMethodCacheExampleAction implements InMethodCacheExample {
      
        public Person getPerson() {
          Context cacheContext = Contexts.getPageContext();
          Person person = cacheContext.get("myPerson");
          if (person==null) {
            // not yet cached => cache it
            person = ... // e,g. database lookup
            cacheContext.set("myPerson", person);
          }
          return person;
        }
      
      }
      



      This improve the initial version: the person property is removed and the cache scope can be different than the component scope. The drawback is that there is even more business-unrelated code.


      It should be noticed that the In-method cache method is very flexible in the sense that you can implement your own caching strategy (LRU, LFU, ...).


      Interceptor cache method



      AFAIK, this approach has not been described in the Seam forum. The idea is to annotate the method whose the result need to be cached. This lead to the following code:


      @Name("interceptorCacheExample")
      @Scope(ScopeType.CONVERSATION)
      @CachedMethods
      public class InterceptorCacheExampleAction implements InterceptorCacheExample {
      
        @CachedMethodResult(ScopeType.PAGE)
        public Person getPerson() {
          Person person = ... // e,g. database lookup
          return person;
        }
      
      }
      



      The @CachedMethods class annotation tells Seam that the component is processed by a specific Interceptor. The @CachedMethodResult annotation tells the Interceptor that the method result is cached. The cache scope can be defined to the standard Seam ScopeType scopes, the cache default scope being ScopeType.PAGE.


      This solution produces clean code and lazy initializing, but it has the drawback on the logging/profiling side: if the result is present in cache, the getPerson() method will not be called. Consequently, if you log something at the beginning/end of the method, your logs will contain only entries for the first call and if you profile, the profiler will tell that the method has been called only one time.


      The magic behind the @CachedMethods and @CachedMethodResult annotations is in the following three files (and also because the InterceptorCacheExampleAction component is in fact a proxy):


      Annotation CachedMethods.java:


      import org.jboss.seam.annotations.intercept.Interceptors;
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Interceptors(CachedMethodResultInterceptor.class)
      public @interface CachedMethods {}
      



      Annotation CachedMethodResult.java:


      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface CachedMethodResult {
        ScopeType value() default ScopeType.PAGE;
      }
      



      Class CachedMethodResultInterceptor.java:


      import org.jboss.seam.annotations.intercept.Interceptor;
      @Interceptor(stateless=true,around={JavaBeanInterceptor.class})
      public class CachedMethodResultInterceptor implements OptimizedInterceptor {
        @AroundInvoke
        public Object aroundInvoke(InvocationContext ic) throw Exception {
          Method m = ic.getMethod();
          CachedMethodResult cmr = m.getAnnotation(CachedMethodResult.class);
          if (cmr!=null) {
            // the method result is cached => get it from the cache (or cache it if absent)
            Context c = cmr.value().getContext();
            String key = CachedMethodResultInterceptor.class.getName()+"#"+m.getDeclaringClass().getName()+"/"+m.getName();
              // Note: the key can be whatever unique value composed by the Interceptor and Method. The above key could be improved
            Object result = c.get(key);
            if (result==null) {
              // result not yet in cache => cache it
              result = ic.proceed();
              c.set(key, result);
            }
            return result;
          } else {
            // the method is not cached => delegate call to the InvocationContext
            return ic.proceed();
          }
        }
      }
      



      When a JSF page calls the getPerson() method, the call is not made directly, but via the Seam interceptors. As the component class is annotated with the @CachedMethods, the CachedMethodResultInterceptor will be inserted in the Seam interceptor chain (because of the around annotation property, the CachedMethodResultInterceptor will be called before the JavaBeanInterceptor). Thus, as the getPerson() method is annotated by @CachedMethodResult, the CachedMethodResultInterceptor will look into the cache if the result is already computed. If cached, the result will be returned from the cache (i.e. no getPerson() method call!). Else, the result will be obtained from the InvocationContext (which will delegate the method call to the JavaBeanInterceptor), cached and returned.


      From the performance/speed perspective, the Invocator cache method is probably a bit slower than the In-method cache method because of the added CachedMethodResultInterceptor and of the cache lookup. However, the code simplicity and cache scope functionality are a great improvement which may worth the little performance drawback (if any).


      You may have been noticed that the InterceptorCacheExampleAction class structure is very similar to the default DefaultExampleAction class structure: only the @CachedMethods and @CachedMethodResult annotations have been added. This means that you can add method result caching at a very low cost.


      Conclusion



      Having reviewed the (some of the) existing method result caching methods, we cannot tell that one is better than another: it depends on the usage. If you are looking for...



       
         
         
         
         
         
       
       
         
         
         
         
         
       
       
         
         
         
         
         
       
       
         
         
         
         
         
       
       
         
         
         
         
         
       
      Method nameClean codeHigh component creation performanceHigh call performanceCustom cache strategie
      Defaultyesyes--
      @Create/@Factory--yes-
      In-method cache-yesyesyes
      Interceptor cacheyesyesyes-


      I hope it helped...

        • 1. Re: How-to: avoid method or getter to be called several times by caching result
          jkronegg

          I've investigated further from the Seam Interceptor chain viewpoint, so here is the result.


          Notes about Introspector cache method



          Having read Dan Allen's paper on improving performance of two magnitude orders, it appears that Seam Interceptors have a big impact on performance. Since  all four methods presented above use them in the background, they are all subject to these performance issues.


          However, the Interceptor cache method adds an Interceptor (which should increase the first call duration) but cuts down the Interceptor chain (which should decrease the subsequent calls duration). At the end, the Interceptor cache method may possibly be the fastest method of the fourth.


          About Interceptor priority:



          • priority is defined using around and within properties

          • client-side Interceptors are always prioritary over server-side Interceptors

          • Seams does not allow to define priority in client-side Interceptors (see JBSEAM-3936)



          Thus, we can fine our CachedMethodResultInterceptor as such (the around having no effect yet due to JBSEAM-3936):


          @Interceptor(stateless=true, type=InterceptorType.CLIENT, around={SynchronizationInterceptor.class})
          



          This cuts the Interceptor chain as early as possible: only the SynchronizationInterceptor and the JavaBeanInterceptor are executed before the CachedMethodResultInterceptor. I could not find the way to bypass them.


          In the tests I've done, the interceptor chain had the following size:



          • initial code without CachedMethodResultIntrospector: 5 Introspectors used

          • with CachedMethodResultIntrospector, first call to getPerson() : 6 Introspectors used

          • with CachedMethodResultIntrospector, next calls to getPerson() : 3 Introspectors used (2 if JBSEAM-3936 is corrected)



          Notes about default method



          As a variant of the default method, the getPerson() getter can be annotated with @BypassInterceptors which prevent all interceptors to be called, improving the Seam internal performance. However, since this is not a caching method, the getPerson() business code efficiency will not be improved for repeated calls. And also you may need Interceptors in the getter code.


          Notes about conclusion



          It may be usefull to add a Seam Introspector chain size property to our comparison table:



           
             
             
           
           
             
             
           
           
             
             
           
           
             
             
           
           
             
             
           
           
             
             
           
          Method nameSeam Introspector chain size
          Default~5
          @BypassInterceptors0
          @Create/@Factory~5
          In-method cache~5
          Introspector cache2-3


          Note that Seam Introspector chain size may vary depending on Seam configuration and on your Component annotations (that's why I indicated ~5).

          • 2. Re: How-to: avoid method or getter to be called several times by caching result
            damonchong

            This is abit late but why not do the below?


            <c:set var="_personName" value="#{createExample.person.name}"/>
            



            within the jsf page? I used it to reduce the call to the backing bean
            when a jsf page is first rendered but it is not perfect as partial
            re-rendering of a page may cause issues.


            Cheers.

            • 3. Re: How-to: avoid method or getter to be called several times by caching result
              jkronegg

              Hi Damon,
              According to http://www.warski.org/blog/?p=10, c:set is only an alias. Thus, using _personName twice will make the EL to be evaluated two times.

              • 4. Re: How-to: avoid method or getter to be called several times by caching result
                yue.neil

                This is a very good summary of caching solutions. However, I found it not really working well if the bean needs to do pagination, e.g. getting 25 rows into the list each time. The list should get updated when jumping to the next page, but since the list exists in cache, such update cannot happen in a correct manner. How should we consider pagination while doing cache?