Search code examples
javaspringdependency-injectionspring-bootjavabeans

Java Bean Composite injection


I have an interface

public interface Abstraction { 
    void execute();
}

I have built a composite implementation and want to registered this object as the bean, @Named

@Named
public class Composite implements Abstraction {
    private List<Abstraction> list;

    @Inject
    public Composite(List<Abstraction> list) {
        this.list = list;
    }

    public void execute() { 
        list.forEach(Abstraction::execute);
    }
}

How do I set it up so that the set of implementations to the abstraction gets injected properly into the Composite above? I will be having another object that takes the abstraction as a dependency and I want it to receive the @Named Composite above with the 2 Implementations below injected into the ctor.

public class Implementation1 implements Abstraction {
    public void execute() { }
}

public class Implementation2 implements Abstraction {
    public void execute() { }
}

Solution

  • If you create a bean for each of your implementations, your example will work out of the box. For example, annotate your implementations with @Named or @Component and mark them for scanning (component scan their package)

    @Configuration
    @ComponentScan
    public class StackOverflow {
        public static void main(String[] args) throws Exception {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(StackOverflow.class);
            System.out.println(ctx.getBean(Composite.class).list);
        }
    }
    
    interface Abstraction {
        void execute();
    }
    
    @Named
    class Composite implements Abstraction {
        List<Abstraction> list;
    
        @Inject
        public Composite(List<Abstraction> list) {
            this.list = list;
        }
    
        public void execute() {
            list.forEach(Abstraction::execute);
        }
    }
    
    @Named
    class Implementation1 implements Abstraction {
        public void execute() {
        }
    }
    
    @Named
    class Implementation2 implements Abstraction {
        public void execute() {
        }
    }
    

    The Composite's list will contain both implementations.

    Alternatively, since you only have two implementations, you could name their beans and inject them separately. For example

    @Component("one")
    class Implementation1 implements Abstraction {
        public void execute() {
        }
    }
    
    @Component("two")
    class Implementation2 implements Abstraction {
        public void execute() {
        }
    }
    

    and inject them in the Composite

    List<Abstraction> list = new ArrayList<>(2);
    
    @Inject
    public Composite(@Qualifier("one") Abstraction one, @Qualifier("two") Abstraction two) {
        list.add(one);
        list.add(two);
    }
    

    I suggest this solution just because the order of initialization of Abstraction beans might mess up your context initialization. For example if Implementation1 somehow depended on the initialization of Composite, the context would complain. This is rare and you can control it in other ways. Still, being explicit about the beans might be clearer in this case.