Search code examples
javadependency-injectionhk2

How to collect several interfaces into a Collection in HK2?


I have my AbstractBinder and I bind several classes with the same interface. Let's say I bind Fish and Cat which both implement Animal interface.

What is the easiest/proper way of injecting them into a bean which takes Collection<Animal> ?

PS: Spring has equivalent in simply @Autowire List<Animal> and the collection is created and populated by Spring.


Solution

  • HK2 has IterableProvider<T>, as mentioned here in the documentation. You can get the service by name, by qualifier annotation, or just iterate over them, as it's an Iterable. Just for fun, here is a test.

    public class IterableProviderTest {
        
        public static interface Service {}
        
        public static class ServiceOne implements Service {}
        
        @QualAnno
        public static class ServiceTwo implements Service {}
        
        @Qualifier
        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        public static @interface QualAnno {
            public static class Instance 
                    extends AnnotationLiteral<QualAnno> implements QualAnno {
                public static QualAnno get() {
                    return new Instance();
                }
            }
        }
        
        public class Binder extends AbstractBinder {
            @Override
            protected void configure() {
                bind(ServiceOne.class).to(Service.class).named("one");
                bind(ServiceTwo.class).to(Service.class).qualifiedBy(QualAnno.Instance.get());
            }  
        }
        
        @Inject
        private IterableProvider<Service> services;
        
        @Test
        public void test_IterableProvider() {
            ServiceLocator locator = ServiceLocatorUtilities.bind(new Binder());
            locator.inject(IterableProviderTest.this);
            
            assertEquals(2, services.getSize());
            
            Service serviceOne = services.named("one").get();
            assertTrue(serviceOne instanceof ServiceOne);
            
            Service serviceTwo = services.qualifiedWith(QualAnno.Instance.get()).get();
            assertTrue(serviceTwo instanceof ServiceTwo);  
        }
    }
    

    UPDATE

    For a List<Service> (to avoid HK2 InterablProvider), the only think I can think of is to use a Factory and inject the IterableProvider into it, and from there return the list. For example

    public class Binder extends AbstractBinder {
        @Override
        protected void configure() {
            ...
            bindFactory(ListServiceFactory.class).to(new TypeLiteral<List<Service>>(){});
        }  
    }
    
    public static class ListServiceFactory implements Factory<List<Service>> {
        
        @Inject
        private IterableProvider<Service> services;
    
        @Override
        public List<Service> provide() {
            return Lists.newArrayList(services);
        }
    
        @Override
        public void dispose(List<Service> t) {}
    }
    

    Yeah it's a little bit of extra work.