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's 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