Search code examples
javadependency-injectionguiceguice-3

How to use Guice's AssistedInject factories along with service loader?


In my guice module I have multiple factories like shown below:

install(new FactoryModuleBuilder().implement(SportsCar.class,Ferrari.class).build(FerrariFactory.class));
install(new FactoryModuleBuilder().implement(LuxuryCar.class,Mercedes.class).build(MercedesFactory.class));

Both the factories have the following create method which takes an assisted element:

Ferrari create(@Assisted Element partsElement);

Mercedes create(@Assisted Element partsElement);

In a CarChooser class, I get instances of Ferrari or Mercedes as shown below:

@Inject 
public CarChooser(FerrariFactory ferrariFactory , MercedesFactory mercedesFactory )
{
        this.ferrariFactory = ferrariFactory;
        this.mercedesFactory = mercedesFactory;
} 

In the same class:

if(type.equals("ferrari"))
    ferrariFactory.create(partsElement);
else if (type.equals("mercedes"))
    mercedesFactory.create(partsElement);
...

Now, what I am trying is to make this CarChooser class open for extension but closed for modification. i.e. If I need to add another Factory, I shouldn't have to declare it as a variable + add it to the constructor + add another if clause for the corresponding new type. I was planning to use ServiceLoader here and declare an interface CarFactory which will be implemented by all factories (such as FerrariFactory, MercedesFactory etc.) and all implementations will have a getCarType method. But how can I call the create method using Service Loader ?

ServiceLoader<CarFactory> impl = ServiceLoader.load(CarFactory.class);

for (CarFactory fac: impl) {
     if (type.equals(fac.getCarType()))
         fac.create(partsElement);
     }
}

Is the right way if it works (I am not even sure if this would work). Or Is there a better way of doing the same ?

Thanks to the first comment on the post, I know that I want to use MapBinder . I wrote a CarFactory which is extended by both FerrariFactory and MercedesFactory. So I add the following:

MapBinder<String, CarFactory> mapbinder = MapBinder.newMapBinder(binder(), String.class, CarFactory.class);

mapbinder.addBinding("Ferrari").to(FerrariFactory.class);
mapbinder.addBinding("Mercedes").to(MercedesFactory.class);

But since the .to portion of the above code is abstract class I get an initialisation error that FerrariFactory is not bound to any implementation. What should I have here to bind it to the correct Assisted Inject Factory declared with the FactoryModuleBuilder ?


Solution

  • So, using a MapBinder along with generics is the solution.

        install(new FactoryModuleBuilder().implement(SportsCar.class,Ferrari.class).build(FerrariFactory.class));
        install(new FactoryModuleBuilder().implement(LuxuryCar.class,Mercedes.class).build(MercedesFactory.class));
    
    
    
        MapBinder<String, CarFactory<?>> mapbinder = MapBinder.newMapBinder(binder(), new TypeLiteral<String>(){}, new TypeLiteral<CarFactory<?>>(){});
    
         mapbinder.addBinding("ferrari").to(FerrariFactory.class);  
         mapbinder.addBinding("mercedes").to(MercedesFactory.class);
    

    The important thing here to note is that this seems to be supported only in Guice 3.0 + JDK 7. For JDK 8, you need Guice 4.0 ! Found this problem on https://github.com/google/guice/issues/904

    Hope that helps.

    More details regarding the solution:

    http://crusaderpyro.blogspot.sg/2016/07/google-guice-how-to-use-mapbinder.html