Search code examples
javadependency-injectionjerseyinversion-of-controlhk2

Two implementations of one interface in Jersey/HK2, reuse first in other


I have an interface with a naive implementation, say

 interface MyService { 
    void doIt();
 } 

 class MyServicePlain implements MyService{

    @Inject
    SomeOtherInterface

    public void doIt () { 
    }
 }

I want to make a cache that caches the doIt, so I wrote a wrapper :

 class CachingMyService implements MyService {

       @Inject 
       MyService inner;

       int cacheThingie;

       public int doIt() {
               if (cached) ... {
                   return cacheThingie;
               } 
               else {
                   result = inner.doIt();
                   addToCache(result);
                   return result;
               }
       }
 }

Then, I add both implementations to my Binder:

   public class ApplicationBinder extends AbstractBinder {

        protected void configure() {
        this.bind(MyServicePlain.class).to(MyService.class).in(Singleton.class).ranked(2);
        this.bind(CachingMyService.class).to(MyService.class).in(Singleton.class).ranked(2);
        }
   }

I get errors complaining about:

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=MyService,parent=CachingMyService},position=-1,optional=false,self=false,unqualified=null,1102650897)

I trief using Qualitifaction like this:

 @Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface NonCached {
}


 @NonCached
 class MyServicePlain implements MyService{
 }

And using that:

  class CachingMyService implements MyService {

       @Inject @NonCached 
       MyService inner;

But that does not work either.

What is the proper way to wrap a caching service like this? And how can I make hk2 choose the proper implementations?


Solution

  • You need to use the qualifiedBy(Annotation) method when you want to qualify different types. You also need to annotate each injection point with a different qualifier annotation.

    First you need the annotations and have a way to get an instance of them (the qualifiedBy method requires an annotation instance)

    @Qualifier
    @Retention(RUNTIME)
    @Target({TYPE, METHOD, FIELD, PARAMETER})
    public @interface NonCached {
    
        class Literal extends AnnotationLiteral<NonCached> implements NonCached {
    
            public static final NonCached INSTANCE = new Literal();
    
            private Literal() {
            }
        }
    }
    
    @Qualifier
    @Retention(RUNTIME)
    @Target({TYPE, METHOD, FIELD, PARAMETER})
    public @interface Cached {
    
        class Literal extends AnnotationLiteral<Cached> implements Cached {
    
            public static final Cached INSTANCE = new Literal();
    
            private Literal() {
            }
        }
    }
    

    Then when you bind them used qualifiedBy

    this.bind(MyServicePlain.class).to(MyService.class)
        .in(Singleton.class).qualifiedBy(NonCached.Literal.INSTANCE);    
    this.bind(CachingMyService.class).to(MyService.class)
        .in(Singleton.class).qualifiedBy(Cached.Literal.INSTANCE);
    

    Then when you inject them, add the applicable qualifier

    @Inject
    @NonCached
    MyService service;
    
    @Inject
    @Cached
    MyService service;