rudolfv
rudolfv

Reputation: 817

How to inject an interface implementation based on annotations at runtime using Google Guice

I have the following scenario:

public interface ServiceClientAdapter {
    SomeData getSomeData()
}

@LegacyServiceClientAdapter
public class MyLegacyServiceClientAdapterImpl implements ServiceClientAdapter {
    public SomeData getSomeData() {
        // implementation          
    }
}

@NewServiceClientAdapter
public class MyNewServiceClientAdapterImpl implements ServiceClientAdapter  {
    public SomeData getSomeData() {
        // implementation          
    }    
}

public class BusinessLogic {
    @Inject
    private ServiceClientAdapter serviceClientAdapter;
}

LegacyServiceClientAdapter and NewServiceClientAdapter are custom annotations.

The implementation for the serviceClientAdapter field will be determined at runtime by whether the user has been migrated from the legacy to the new service or not.

What is the best way to accomplish this dependency injection using Google Guice?

Take into account that different BusinessLogic classes will exist, each with their own (different) ServiceClientAdapter-like interface and corresponding legacy and new implementation classes.

Ideally this should be done with a piece of framework code that can be used across all use cases.

Upvotes: 1

Views: 1490

Answers (1)

The111
The111

Reputation: 5867

I'm going to assume that the result of your LDAP call can be represented as a string, let's say "legacy" or "new". If not, hopefully you should still be able to adapt this example.

In your module, use a MapBinder:

public class BusinessLogicModule {
    @Override
    protected void configure() {
        // create empty map binder
        MapBinder<String, ServiceClientAdapter> mapBinder =
                MapBinder.newMapBinder(
                        binder(), String.class, ServiceClientAdapter.class);

        // bind different impls, keyed by descriptive strings
        mapBinder.addBinding("legacy")
                .to(MyLegacyServiceClientAdapterImpl.class);
        mapBinder.addBinding("new")
                .to(MyNewServiceClientAdapterImpl.class);
    }
}

Now you can inject a map of instances (or a map of providers of instances if you need to keep creating new instances) into your main class and use the string discovered at runtime to control which kind of instance you get.

public class BusinessLogic {
    @Inject
    private ServiceClientAdapter serviceClientAdapter;

    @Inject
    private Map<String, ServiceClientAdapter> mapBinder;

    public void setupAndUseClientAdapter() {
        String userType = getUserTypeFromLdapServer();
        serviceClientAdapter = mapBinder.get(userType);
        if (serviceClientAdapter == null) {
            throw new IllegalArgumentException(
                    "No service client adapter available for " +
                    userType + " user type.";
        }
        doStuffWithServiceClientAdapter();
    }
}

Upvotes: 1

Related Questions