6

I have a bean which implements two interfaces. The barebones code is as follows:

interface InterfaceA {
  ...
}

interface InterfaceB {
  ...
}

public class ClassC implements InterfaceA, InterfaceB {
  ...
}

In my AppConfig I am specifying the following:

@Bean(name = "InterfaceA")
public InterfaceA interfaceA() {
    return new ClassC();
}

@Bean(name = "InterfaceB")
public InterfaceB interfaceB() {
    return new ClassC();
}

And I use it so:

public class MyClass {

    @Inject
    private final InterfaceA a;

    public MyClass(@Named("InterfaceA") InterfaceA a) {
        this.a = a;
    }
     ...
}

However, Spring complains that:

No qualifying bean of type [com.example.InterfaceA] is defined: expected single matching bean but found 2: InterfaceA, InterfaceB

Similar question was asked and answered for EJB here but I could not find anything for Spring beans. Anybody know the reason?

The workaround is to introduce a new interface which extends both InterfaceA and InterfaceB and then let ClassC implement that. However, I am loath to change my design because of framework constraints.

Community
  • 1
  • 1
341008
  • 9,862
  • 11
  • 52
  • 84
  • Why... Just create a single instance of `ClassC`... The return type of the method doesn't matter here, as the bean will be both `InterfaceA` and `InterfaceB`. You don't need an additional interface... – M. Deinum Jul 20 '15 at 11:06

2 Answers2

6

Thank you for your excellent question.

In my case, I created an interface that extends both A and B:

public interface InterfaceC extends InterfaceA, InterfaceB {}

... and the common implementation implements the unified interface:

public class ClassC implements InterfaceC {
  //...
}

This unified interface allows then to create a single bean:

@Bean
public InterfaceC implementationForAandB() {
    return new ClassC();
}

The Spring framework is then able to inject or autowire the common implementation to dependencies expressed in terms of the primary interfaces:

public class MyClass {

    @Inject
    private final InterfaceA a;

    @Inject
    private final InterfaceB b;

    public MyClass(InterfaceA a, InterfaceB b) {
        //...
    }

}

adelinor
  • 703
  • 9
  • 15
4

Spring is right ... When you write

@Bean(name = "InterfaceA")
public InterfaceA interfaceA() {
    return new ClassC();
}

@Bean(name = "InterfaceB")
public InterfaceB interfaceB() {
    return new ClassC();
}

Spring creates to ClassC objects, one named InterfaceA, the other InterfaceB, both implementing InterfaceA and InterfaceB.

Then when you write :

@Inject
private final InterfaceA a;

you ask Spring to find a bean implementing InterfaceA, but as said above there are 2 so the error.

You could either create only one object of type ClassC, or use @Qualifier or @Named annotations :

@Inject
@Named("InterfaceA")
private final InterfaceA a;

That way, you explicitely ask Spring to find the bean named InterfaceA, and hopefuly it is now unique.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thanks. Adding `@Named` to the field fixed it. I will accept your answer. However, I did annotate the constructor parameter with `@Named`. Why was that not sufficient? Didn't that tell Spring to use the correct bean? – 341008 Jul 20 '15 at 11:57
  • @341008 : If you bean creation uses the constructor with the `@Named` annotation, it should work ... provided you remove the `@Inject` on `a` attribute. If not, as soon as the `MyClass` object is constructed, Spring honours the `@Inject` annotation and tries to set `a` with a `InterfaceA` bean and finds 2 of them. – Serge Ballesta Jul 21 '15 at 06:10
  • Oh, I see. I was setting `@Named` for the constructor parameter and `@Inject` on the field. I will have to remove `@Inject` then. Thanks. – 341008 Jul 21 '15 at 06:29