Saturday, December 3, 2016

Practical guide to Guice - part 2

This post sequels the previous one. It explains how to define multiple bindings, what is AssistedInject and how it may be used for dynamic injection.

Multiple bindings and binding annotations


The interface NamedServiceProvider is implemented by classes NamedServiceA and NamedServiceB:
 public interface NamedServiceProvider {
      String getName();
 }
 public class NamedServiceA implements NamedServiceProvider {
      @Override
      public String getName() {
           return this +":ServiceA";   
      } 
 } 
 public class NamedServiceB implements NamedServiceProvider {
      @Override
      public String getName() {
           return this +":ServiceB";   
      } 
 } 
Each implementation of NamedServiceProvider should have a binding. If the bindings are defined this way:
 public class AppModule extends AbstractModule {  
      @Override  
      protected void configure() {
           bind(NamedServiceProvider.class).to(NamedServiceA.class);
           bind(NamedServiceProvider.class).to(NamedServiceB.class);
      }  
 }
Guice throws run-time exception, since it cannot resolve, which binding should be taken while injecting an instance of the NamedServiceProvider.

Multiple interface binding should be done with binding annotations, so that each binding entry is assigned with a unique tag. There are two types of binding annotations: custom annotations and build-in @Named annotation.

Custom binding annotations


Custom binding annotation should be implemented with some boilerplate code. The custom annotations @ServiceA and @ServiceB are implemented with almost identical code with the only difference in the @interface name:
 import com.google.inject.BindingAnnotation;  
 import java.lang.annotation.Target;  
 import java.lang.annotation.Retention;  
 import static java.lang.annotation.RetentionPolicy.RUNTIME;  
 import static java.lang.annotation.ElementType.PARAMETER;  
 import static java.lang.annotation.ElementType.FIELD;  
 import static java.lang.annotation.ElementType.METHOD;  
 @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)  
 public @interface ServiceA {}  
 import com.google.inject.BindingAnnotation;  
 import java.lang.annotation.Target;  
 import java.lang.annotation.Retention;  
 import static java.lang.annotation.RetentionPolicy.RUNTIME;  
 import static java.lang.annotation.ElementType.PARAMETER;  
 import static java.lang.annotation.ElementType.FIELD;  
 import static java.lang.annotation.ElementType.METHOD;  
 @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)  
 public @interface ServiceB {}  
Binding with a custom binding annotation is done like this:
 public class AppModule extends AbstractModule {  
      @Override  
      protected void configure() {
           bind(NamedServiceProvider.class).annotatedWith(ServiceA.class).to(NamedServiceA.class);
           bind(NamedServiceProvider.class).annotatedWith(ServiceB.class).to(NamedServiceB.class);
      }  
 }
Injection with a custom binding annotation is done like this:
 public class ServiceAConsumer implements NamedServiceConsumer {  
      private NamedServiceProvider service;  
      @Inject   
      public ServiceAConsumer (@ServiceA NamedServiceProvider service) {  
           this.service = service;  
      }  
      @Override  
      public NamedServiceProvider getService() {  
           return service;  
      }  
 }  

Build-in @Named annotation


Guice has a build-in binding annotation @Named, which uses a string as a binding tag. Bindings with @Named annotation is done like this:
 public class AppModule extends AbstractModule {  
      @Override  
      protected void configure() {
           bind(NamedServiceProvider.class).annotatedWith(Names.named(AppNames.SERVICE_A)).to(NamedServiceA.class);
           bind(NamedServiceProvider.class).annotatedWith(Names.named(AppNames.SERVICE_B)).to(NamedServiceB.class);
      }  
 }

 public final class AppNames {
      public final static String SERVICE_A = "serviceA";
      public final static String SERVICE_B = "serviceB";
 }
An instance injection should be done like this:
 public class ServiceAConsumer implements NamedServiceConsumer {  
      private NamedServiceProvider service;  
      @Inject   
      public ServiceAConsumer (@Named(AppNames.SERVICE_A)NamedServiceProvider service) {  
           this.service = service;  
      }  
      @Override  
      public NamedServiceProvider getService() {  
           return service;  
      }  
 }  
Binding with custom annotation is checked in compile time, while @Named annotation inconsistency will be detected only in run-time. Still there are use-cases, when @Named annotation is more appropriate solution.

AssistedInject


Constructor injection works as following: Guice injects all dependencies and then sends them as the constructor arguments.
Sometimes more parameters are needed for object creation and a constructor receives a mixture of injected and other parameters. Guice way to create an object with partially injected constructor arguments is called AssistedInject.

AssistedInject:
1. Directs Guice, which of constructor arguments should be injected and which are sent by the caller.
2. Provides API, which receives the caller arguments.
3. Provides creation method instead of injection.

The class FlexibleServiceProxy has a constructor, which receives two arguments: injectedService and displayedName.
 public class FlexibleServiceProxy implements ServiceProxy {  
      private String displayedName;  
      private ServiceProvider injectedService;  
      @Inject   
      public FlexibleServiceProxy(ServiceProvider injectedService, @Assisted String displayedName) {  
           this.injectedService = injectedService;  
           this.displayedName = displayedName;  
      }  
 }  
The first argument is injected by Guice. The @Assisted annotation before the second argument directs Guice, that the argument is received from the caller and should be posted to the constructor.

Instance of the FlexibleServiceProxy cannot be injected, since there is no way to send arguments values to the injector. The class injection is replaced by the factory injection.

The factory interface ServiceProxyFactory defines a method createServiceProxy. It returns the constructed object of type ServiceProxy. The method's parameter is the non-injected argument, required by the constructor:
 public interface ServiceProxyFactory {
      ServiceProxy createServiceProxy(String serviceName);
 }
The factory should be installed in the module; this substitutes binding of the FlexibleServiceProxy:
 public class AppModule extends AbstractModule {  
      @Override  
      protected void configure() {
           bind(ServiceProvider.class).to(ServiceBean.class);
           install(new FactoryModuleBuilder()
                .implement(ServiceProxy.class, FlexibleServiceProxy.class)
                .build(ServiceProxyFactory.class));
      }  
 }
Definition and creation of an instance of the FlexibleServiceProxy is done like this:
 ServiceProxyFactory factory = DIContainer.getInjector().getInstance(ServiceProxyFactory.class);
 ServiceProxy proxy1 = factory.createServiceProxy("serviceABC");

Dynamic injection with AssistedInject


Type of the injected variable is known in compile time. Sometimes decision on a type to be injected should be taken in run-time. AssistedInject allows implement dynamic injection.

The FlexibleServiceProxy was modified. Now it used two services. The service of type ServiceProvider is injected by Guice upon creating of the FlexibleServiceProxy. The constructor string argument specifies a name of the NamedServiceProvider service to be injected explicitly:
 public class FlexibleServiceProxy implements ServiceProxy {  
      private NamedServiceProvider otherService;  
      private ServiceProvider injectedService;  
      @Inject   
      public FlexibleServiceProxy(ServiceProvider injectedService, @Assisted String stringValue) {  
           System.out.println("serviceName:" + otherServiceName);  
           this.injectedService = injectedService;  
           otherService = DIContainer.getInjector().getInstance(Key.get(NamedServiceProvider.class, Names.named(otherServiceName)));  
           System.out.println(otherService.getName());  
      }  
      @Override  
      public NamedServiceProvider getNamedService() {  
           return otherService;  
      }  
      public ServiceProvider getInjectedService() {  
           return injectedService;  
      }  
 }  
See working examples on Git.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top