The reasons may be various like creation is time/resource consuming (database connection etc.) and we want to speed up application startup.
This is a use case for the lazy initialization pattern.
We have a a following service interface and implementation that returns current time:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface Service { | |
LocalDateTime getTime(); | |
} | |
public class DefaultService implements Service { | |
private static final Logger logger = LoggerFactory.getLogger(DefaultService.class.getName()); | |
public DefaultService() { | |
logger.debug("Initializing service"); | |
} | |
@Override | |
public LocalDateTime getTime() { | |
return now(); | |
} | |
} |
Let's imagine that the initialization is very costly.
Simple lazy initialization could look as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LazyInitService implements Service { | |
private Service service; | |
@Override | |
public LocalDateTime getTime() { | |
initializeIfNecessary(); | |
return service.getTime(); | |
} | |
private void initializeIfNecessary() { | |
if (service == null) { | |
service = new DefaultService(); | |
} | |
} | |
} |
This solution is fine in single threaded environment (we will cover multi threaded environment later).
The problem arises when the service does not have few methods but a lot of them (it may happen when you're using some legacy or not well designed library).
Such implementation would look awful in this situation - you would have to implement every method and try to initialize the object in all of them.
In that case we can create a proxy object that would lazy initialization for each method invocation.
To create such proxy we need to implement java.lang.reflect.InvocationHandler interface.
There are some pitfalls the we need to overcome (more info here), that's why it's easier to use Guava's AbstractInvocationHandler:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LazyInitWithProxyServiceFactory { | |
public static Service createService() { | |
return Reflection.newProxy(Service.class, new LazyInitInvocationHandler()); | |
} | |
private static class LazyInitInvocationHandler extends AbstractInvocationHandler { | |
private Service service; | |
@Override | |
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable { | |
return method.invoke(service(), args); | |
} | |
private Service service() { | |
if (service == null) { | |
service = new DefaultService(); | |
} | |
return service; | |
} | |
} | |
} |
The only thing left is thread safety. We can implement it on our own, but the safest way is to use ConcurrentInitializer from commons-lang:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LazyInitWithProxyThreadSafeServiceFactory { | |
public static Service createService() { | |
return Reflection.newProxy(Service.class, new ThreadSafeLazyInitInvocationHandler()); | |
} | |
private static class ThreadSafeLazyInitInvocationHandler extends AbstractInvocationHandler { | |
private static final LazyInitializer<Service> initializer = new LazyInitializer<Service>() { | |
@Override | |
protected Service initialize() { | |
return new DefaultService(); | |
} | |
}; | |
@Override | |
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable { | |
return method.invoke(initializer.get(), args); | |
} | |
} | |
} |
I chose LazyInitializer that uses double-checked idiom but there are other implementation that might be more useful depending on the context.
Let's run all the implementations and see how they work (notice when the info about initialization is printed on the console) :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class App { | |
private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); | |
public static void main(String[] args) { | |
invoke(new DefaultService()); | |
logger.debug("----------------------\n"); | |
invoke(new LazyInitService()); | |
logger.debug("----------------------\n"); | |
invoke(LazyInitWithProxyServiceFactory.createService()); | |
logger.debug("----------------------\n"); | |
invoke(LazyInitWithProxyThreadSafeServiceFactory.createService()); | |
} | |
private static void invoke(Service service) { | |
logger.debug("Invoking method"); | |
logger.debug("Current time:" + service.getTime().toString()); | |
} | |
} |
The whole project can be found on github.