F.P
F.P

Reputation: 17831

Dynamically find service by name in Jersey (HK2)

In my application I need to acquire a different implementation depending on some user input.

As I want to take full advantage of HK2 I want to solve this with the methods Jersey/HK2 provide.

So far, all I ever did was inject services via interfaces that were bound to implementations on the startup using ApplicationConfig and ApplicationBinder:

@javax.ws.rs.ApplicationPath("api")
public class ApplicationConfig extends ResourceConfig
{
    public ApplicationConfig()
    {
        super();
        packages(true, "my.package");
        register(new ApplicationBinder());
        register(....);
        ....
    }
}

public class ApplicationBinder extends AbstractBinder
{
    @Override
    protected void configure()
    {
        bind(ServletTemplateLoader.class).to(TemplateLoader.class);
        bindAsContract(JobsImpl.class);
        bindAsContract(JobInputAppender.class);
        bindAsContract(ParamNameMapper.class);
        bind(RedisJobRepository.class).to(JobRepositoryInterface.class);
        ....
    }

Now however, I need to acquire an implementation dynamically depending on user input. There are 25 different implementations all using the same interface.

This means, I can no longer simply use the bind.to approach. Instead I reckon I need to register them all individually with bindAsContract.

But then, how do I write a method/class that for any given input (from the user) will provide me with the correct implementation?

Essentially, I need a method that looks like this:

public interface MyInterface {}
public class Type1Impl implements MyInterface {} // registered with `bindAsContract`

public MyInterface getImplementation(final String type_)
{
    switch (type_) {
        case "type1":
            return // what to do here to get "my.package.Type1Impl" instance?
        case "type":
            ....
    }
}

I need the instance to come from HK2 because the Impl also uses injected services, so I can't simply create a new instance on the fly.

Upvotes: 0

Views: 895

Answers (2)

jwells131313
jwells131313

Reputation: 2394

I think there is a better answer using IterableProvider. Basically you can do this in one of your services:

public class ImplementationGetter {
  @Inject
  private IterableProvider<MyInterface> interfaceProvider;

  public MyInterface getImplementation(final String type_) {
    return interfaceProvider.named(type_).get();
  }
}

Hope this helps!

Upvotes: 2

F.P
F.P

Reputation: 17831

So after several hours of searching without answer I got frustrated and circled back, thinking "ok just try to do the most obvious thing you can think of".

Which, in the case of DI is to just tell the container to give me what I want.

As it turns out, this works and is almost emberrassingly trivial...

public interface MyInterface {}
public class Type1Impl implements MyInterface {}
public class Type2Impl implements MyInterface {}

@javax.ws.rs.ApplicationPath("api")
public class ApplicationConfig extends ResourceConfig
{
    public ApplicationConfig()
    {
        super();
        packages(true, "my.package");
        register(new ApplicationBinder());
    }
}

public class ApplicationBinder extends AbstractBinder
{
    @Override
    protected void configure()
    {
        bindAsContract(ImplementationGetter.class);
        bind(Type1Impl.class).to(MyInterface.class).named("type1");
        bind(Type2Impl.class).to(MyInterface.class).named("type2");
    }
}

public class ImplementationGetter {
    @Inject
    private ServiceLocator _locator;

    public MyInterface getImplementation(final String type_)
    {
        switch (type_) {
            case "type1":
                return _locator.getService(MyInterface.class, "type1");
            case "type2":
                return _locator.getService(MyInterface.class, "type2");
        }
    }
}

Upvotes: 1

Related Questions