In this first article I want to describe a dynamic service client that performs lazy service dependency resolution (lookup) and that can be injected into a CDI injection point.
It is a good base for being extended to use sophisticated configuration or error handling mechanisms.
Defining a Dynamic Service Locator
In JEE6 its quite easy to perform a JNDI lookup for an EJB using the @EJB annotation. Unfortunately this is done during construction of the service consumers. When you have a multi-module project where each module should be deployable independently from the others, deployment will fail if the dependencies defined by the @EJB annotation could not be satisfied, that is, the EJB lookup fails.The key to this problem is a lazy lookup, that is done first time the dependency is actually needed. But that is not supported by the @EJB annotation.
In our current project we solved the problem by implementing a dynamic service proxy that implements the interface of the service and performs the lookup using a JNDI lookup or another strategy on the first actual service call.
Creating a Proxy
Creating a dynamic proxy is relatively simple, you need a custom implementation of an InvocationHandler that performs the actual method invocation and need to specify the interfaces the dynamic proxy should implement. A factory method is used to create such a proxy instance. The the following code example shows such a factory method:public abstract class DynamicServiceClient { public static <T> T newInstance(final Class T serviceInterface) { return (T) java.lang.reflect.Proxy.newProxyInstance( getClassLoader(), new Class[] { serviceInterface }, new ProxyServiceInvocationHandler T(config)); } private static ClassLoader getClassLoader(){ ... } }
Invocation Handler
The invocation handler delegates all service call to an instance of the actual service. The service is kept as an instance field of the invocation handler that is initialized upon the first service call using a Service Locator. The invoke method of the Invocation Handler delegates the call to the service instance.class ProxyServiceInvocationHandler<T> implements InvocationHandler { /** * The service reference that is lazily initialized */ private T service; /** * The service locator that performs the actual lookup */ private final ServiceLocator<T> locator; ... @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { //do the lookup if not already done if (this.service == null) { this.service = this.locator.locate(); } //do the actual service call and return the result return method.invoke(this.service, args); } }
Service Locator
The service locator that is used in the the above listing performs the actual lookup. In our project I used an implementation that allowed to define a location strategy in a configuration, with the JNDI lookup being implemented in one of the strategies. The following example however shows a simple implementation that performs a straightforward JNDI lookup using a lookupName.public class ServiceLocator<T>{ /** * the name for the JNDI lookup */ private String lookupName; ... public T locate() { final Context context = new InitialContext(); T service = (T)context.lookup(this.lookupName); return service; } }
Service Injection
In order to use the dynamic service shown above and inject it via CDI, I used a producer method that creates instances of the dynamic service and a qualifier to distinguish the dynamic service client from the actual service implementation that might be available in the CDI container as well.The creation of the instance itself is done by the static factory method shown in the first listing. The producer extends the DynamicServiceClient I described earlier and uses its static factory method. In order to keep the producer class simple, we need to extend some methods to the DynamicServiceClient.
The extended DynamicServiceClient that is shown in the following listing contains the logic for the actual service proxy instantiation and an abstract method returning the interface class of the service to be instantiated that has to be implemented by the producer.
public abstract class DynamicServiceClient<T>{ /** * Instance of the service client */ private T serviceInstance; /** * The method has to return the interface of the service */ protected abstract Class<T> getServiceInterface(); /** * Creates a new instance of the service client. */ protected T getInstance() { if (serviceInstance == null) { serviceInstance = newInstance(getServiceInterface()); } return serviceInstance; } /** * The factory method to create a new service instance */ public static <T> T newInstance(final ClassserviceInterface) { return (T) Proxy.newProxyInstance( getClassLoader(), new Class[] { serviceInterface }, new ProxyServiceInvocationHandler ()); } } private static ClassLoader getClassLoader(){ ... } }
To qualify an injection point and the producer method to create and inject instances of the service client that implement the service interface and not instances of the actual service, I used a qualifier as shown in the next listing.
@Qualifier @Retention(RUNTIME) @Target({ METHOD, FIELD, PARAMETER }) public @interface ServiceClient {}
Now we're ready to define a producer using the DynamicServiceClient as base class. The producer is relatively simple as it only contains two simple methods.
public class MyServiceClientProducer extends DynamicServiceClient<MyBusinessService> { @Produces @ServiceClient public MyBusinessService getInstance() { return super.getInstance(); } protected Class<MyBusinessService> getServiceInterface() { return MyBusinessService.class; } }To inject the dynamic service client into an injection point we simply have to define the Service interface class and use the qualifier. That's it.
public class MyServiceConsumer { @Inject @ServiceClient private MyBusinessService service; ... }
Instead of
public class MyServiceConsumer { @EJB(lookup="...") private MyBusinessService service; ... }
No comments:
Post a Comment