Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Autowire Annotation with Several Interface Implementations

Tags:

java

spring

Suppose you have one interface

public interface A {
  public void doSomething();
}

and two implementation classes

@Component(value="aImpl1")
public class AImpl1 implements A {

}

@Component(value="aImpl2")
public class AImpl2 implements A{

}

And finally a class that will use an "A" implementation:

@Component
public class MyClass {
  @Autowire
  A a;
}

Now if I want to inject AImpl1 I add the @Qualifier("aImpl1") while if I want to inject AImpl2 I add @Qualifier("aImpl2")

The question is: Is it possible to instruct spring somehow to look up all implementations of "A" in this case AImpl1 and AImpl2 and use some application specific conventions to choose the most appropriate implementation? for example in this case my convention could be use the implementation with the greatest suffix (i.e. AImpl2)?

EDIT: the class MyClass should not be aware at all about the implementation lookup logic, it should just find its property "a" set with an object of AImpl2.

like image 825
Omar Al Kababji Avatar asked Jul 10 '13 22:07

Omar Al Kababji


People also ask

Can we use @autowired for interface?

If you try to use @Autowired on an interface, the Spring framework would throw an exception as it won't be able to decide which implementation class to use.

Why is @autowired annotation not recommended?

If we had used Autowired, we would have been getting the following error in the test. Therefore, it is not recommended to use Autowired. Safety — Forces Spring to provide mandatory dependencies. We make sure that the created objects are valid after construction.

Can you Autowire byType when more than one bean with the same?

Autowiring using property type. Allows a property to be autowired if exactly one bean of property type exists in the container. If more than one exists, it's a fatal exception is thrown, which indicates that you may not used byType autowiring for that bean.

Can we Autowire an interface without the implemented class?

Could we autowire an interface without any implementation in Spring? No. You are trying to create instance for an Interface which is not possible in Java.


2 Answers

You can inject all implentations as List:

@Autowired
List<A> as;

or as Map with bean name as key:

@Autowired
Map<String, A> as; 

and then choose proper implementation manually (perhaps, in a setter method):

@Autowired
public void setAs(Map<String, A> as) {
    this.a = ...;
}
like image 88
axtavt Avatar answered Nov 15 '22 13:11

axtavt


Assuming you already have hundreds of interfaces and implementations (as you said in a comment), and you do not want to refactor all the code... then is a tricky problem... and this is a tricky solution:

You could create a custom BeanDefinitionRegistryPostProcessor and implement either the method postProcessBeanDefinitionRegistry or postProcessBeanFactory.

This way you have access to all bean definitions before they are instantiated and injected. Do your logic to find which is the preferred implementation for each one of your interfaces, and then, set that one as primary.

@Component
public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(
            BeanDefinitionRegistry registry) throws BeansException {

          // this method can be used to set a primary bean, although 
          // beans defined in a @Configuration class will not be avalable here.

    }

    @Override
    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactory) throws BeansException {     

        // here, all beans are available including those defined by @configuration, @component, xml, etc.

        // do some magic to somehow find which is the preferred bean name for each interface 
        // you have access to all bean-definition names with: beanFactory.getBeanDefinitionNames()
        String beanName = "aImpl2"; // let's say is this one

        // get the definition for that bean and set it as primary
        beanFactory.getBeanDefinition(beanName).setPrimary(true)

    }



}

The hard part is to find the bean name, it depends of the specifics of your application. I guess that having a consistent naming convention will help.

Update:

It seems that both methods in the interface BeanDefinitionRegistryPostProcessor can be used for this purpose. Having in mind that in the postProcessBeanDefinitionRegistry phase, beans configured through @configuration classes are not yet available, as noted in the comments below.

On the other hand they are indeed available in postProcessBeanFactory.

like image 44
garst Avatar answered Nov 15 '22 13:11

garst