Using run-time bytecode instrumentation (BCI)

2011-03-01

One of my classes in a project has an internal Maps (HashMaps), which depending on the program flow will or will not be used. Constructing the Maps is very CPU and memory intensive, so i only want to initialize the maps when it is needed. The information in the map is obtained in various ways: getPartners, getPartnerBySalesForceId, getPartnerBy, getPartnerByName etc. All of these methods use the internal maps. A previous developer placed this code of code in all of these "get"-methods:

public Partner getXxxx(){
  if (!isInitialized) initialize();
}

I just had to add another getXxx type method to obtain a partner using a different qualifier. My unit test failed instantly after completing the code, complaining over a NullPointerException. Of course i did not add the initialise check code.

A much better approach is to use a proxy pattern here and create a proxy that checks on "each" method call if the object has been initialized (each might be better read as "all get-methods"). This can be done using the standard Java 1.3 Proxy class, in which case you need an interface (Proxies in Java SE are based on the interface). I decided to the

CGLIB instead make run-time bytecode instrumentation (BCI) possible.

Here is the code:

public static PartnerManager getInstance(){
    Enhancer e = new Enhancer();
    e.setSuperclass(PartnerManager.class);
    e.setCallback(new MethodInterceptor() {
      public Object intercept(Object target, Method method, 
           Object[] args, MethodProxy methodProxy) 
                                                      throws Throwable {
       if (method.getName().startsWith("get")){ // or use  custom annotations
       PartnerManager p = (PartnerManager) target;
        if (!p.isInitialized()){
          p.init();
        }
       }
        return methodProxy.invokeSuper(target, args);
      }
    } );

    return (PartnerManager) e.create();

  }

Line 1 create a CGLIB Enhancer (a class which "Generates dynamic subclasses to enable method interception"), line 2 instructs what the super class is of the run-time generated class and line 3 sets the Callback handler (in this case a MethodInterceptor). The interceptor checks if the method name starts with "get" (a better alternative is to use an custom annotation with a runtime retention, and check if the method has been (or has no been) annotated). If it is a get-method, a check is made if the PartnerManager is initialized, if not it is initialized (this example uses an isInitialized method, but could have read also the field(s) containing the map)

This solution is much more robust than the manual instrumentation... (note that other solutions would be to use AOP, Spring, etc - but this is quite easy as well :) )

Update, please see this entry for a running demo and a more generic solution

This article does not necessarily reflect the technical opinion of EDC4IT, but purely of the writer. If you want to discuss about this content, please use thecontact ussection of the site