Spring – Usage of @Primary annotation or attribut for inject default bean instances

For using dependency injection with Spring 3.0, several ways exists. You can use the annotation based way with the annotations @Service, @Repository, @Component or the JSR 330 @Named annotation or define the beans in xml with autowiring attributes or last but not least define the beans in a Java configuration. To wire up the injection targets you can use the @Autowired or the JSR 330 @Inject annotation or wire up the target in the XML bean definition.

Well the sense of this post isn’t to explain Spring dependency injection in deep. I will rather discuss what happens if Spring found multiple beans qualified for injection, and how you can help Spring to make the decision which is the right one bean. I use the JSR 330 annotations, The examples also works with the Spring annotations @Service and @Autowire.

Visit spring-practice project at my github account to download the code.

First we code an interface BaseService and two classes SingleServiceImpl_1 and SingleServiceImpl_2 which implement this interface.

One Interface two implementations

<br />public interface SingleService {<br />	void callMe();<br />}<br /><br />@Named<br />public class SingleServiceImpl_1 implements SingleService {<br />	@Override<br />	public void callMe() {<br />	}<br />}<br /><br />@Named<br />public class SingleServiceImpl_2 implements SingleService {<br />	@Override<br />	public void callMe() {<br />	}<br />}<br />

In another Spring bean we want to inject an instance of SingleService

<br />@Inject<br />public void setSingleService(SingleService singleService) {<br />	this.singleService = singleService;<br />}<br />

OK, but Spring will found more than one implementation of SingleService, so we get an Execption at startup

org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [ch.nydi.spring.example.SingleService] is defined: expected single bean but found 2: singleServiceImpl_1,singleServiceImpl_2

We have two opportunities to solve this problem. By Use the annotation @Primary we tell Spring which autowire candidate will preferred when more than one matching bean instance exists.

<br />@Named<br />@Primary<br />public class SingleServiceImpl_1 implements SingleService {<br />	@Override<br />	public void callMe() {<br />	}<br />}<br />

or we explicitly name the instance which we prefer at injection. If the beans @Named annotation has no value the class name with lower case first letter will taken as default bean name.

<br />@Inject @Named("singleServiceImpl_1")<br />public void setSingleService(SingleService singleService) {<br />	this.singleService = singleService;<br />}<br />

I prefer to use the @Primary annotation, so in most cases the @Named annotation with the @Inject is not necessary.

Interface Inheritance

In case of interface inheritance we have to consider, that Spring will respect all implemented interfaces for binding.

<br />public interface BaseService {<br />	void callBase();<br />}<br /><br />public interface InheritedService extends BaseService {<br />	void callInherited();<br />}<br /><br />@Named<br />public class BaseServiceImpl implements BaseService {<br /><br />	@Override<br />	public void callBase() {<br />	}<br />}<br /><br />@Service<br />public class InheritedServiceImpl<br />	extends BaseServiceImpl implements InheritedService {<br /><br />	@Override<br />	public void callInherited() {<br />	}<br />}<br />

The code for inject the instances:

<br />@Inject<br />public void setBaseService(BaseService baseService) {<br />    this.baseService = baseService;<br />}<br /><br />@Inject<br />public void setInheritedService(InheritedService inheritedService) {<br />    this.inheritedService = inheritedService;<br />}<br />

The injection of BaseService service will also throw the same exception as noticed above. Spring will detect BaseServiceImpl and InheritedServiceImpl as implementations of BaseService. The injection of InheritedService will work because only one implementation of InheritedService is defined.

As previously you will also have two opportunities to solve this problem.

<br />@Named<br />@Primary<br />public class BaseServiceImpl implements BaseService {<br /><br />	@Override<br />	public void callBase() {<br />	}<br />}<br />

or with @Named

<br />@Inject @Named("baseServiceImpl")<br />public void setBaseService(BaseService baseService) {<br />    this.baseService = baseService;<br />}<br />

At the bottom of this post you will will find a JUnit test to play around with the above services.

Resolve of primary autowiring candidates with ApplicationContext

The usage of Spring ApplicationContext (or BeanFactory) is another way to resolve Spring beans. The really need of direct ApplicationContext use is another discussion. For example with the methods

T getBean(Class requiredType)
T getBean(String name, Class requiredType)
Object getBean(String name)

of BeanFactory you can resolve a Spring bean by type and/or name.

If you use type and instance name Spring resolves the correct instance.

<br />applicationContext.getBean("singleServiceImpl_1", SingleService.class);<br />applicationContext.getBean("singleServiceImpl_2", SingleService.class);<br />applicationContext.getBean("baseServiceImpl", BaseService.class);<br />applicationContext.getBean("inheritedServiceImpl", InheritedService.class);<br />

Similar to the injection mechanism getBean(…) should use the primary attribute to resolve the bean instance if no bean name is given.

<br />applicationContext.getBean(SingleService.class); // should return the SingleServiceImpl_1 instance<br />applicationContext.getBean(BaseService.class); // should return the BaseServiceImpl instance<br />applicationContext.getBean(InheritedService.class); // should return the InheritedServiceImpl instance<br />

unfortunately the first two statements will cause an org.springframework.beans.factory.NoSuchBeanDefinitionException because the underlying DefaultListableBeanFactory.getBean(Class requiredType) doesn’t respect the primary attribute of BeanDefinition. I placed a Jira for this problem SPR-7854.

As a workaround solution I wrote a extended DefaultListableBeanFactory and use it in a extended ClassPathXmlApplicationContext (for application startup) and TestGenericXmlContextLoader (Test context loader).

Extended DefaultListableBeanFactory

<br />public class PrimaryResolverListableBeanFactory extends<br />		DefaultListableBeanFactory {<br /><br />	public PrimaryResolverListableBeanFactory() {<br />		super();<br />	}<br /><br />	public PrimaryResolverListableBeanFactory(BeanFactory parentBeanFactory) {<br />		super(parentBeanFactory);<br />	}<br /><br />	@Override<br />	public  T getBean(Class requiredType) throws BeansException {<br />		Assert.notNull(requiredType, "Required type must not be null");<br />		String[] beanNames = getBeanNamesForType(requiredType);<br />		String primaryCandidate = null;<br />		if (beanNames.length &amp;gt; 1) {<br />			ArrayList autowireCandidates = new ArrayList();<br /><br />			for (String beanName : beanNames) {<br />				BeanDefinition beanDefinition = getBeanDefinition(beanName);<br />				if (beanDefinition.isAutowireCandidate()) {<br />					autowireCandidates.add(beanName);<br />					if(beanDefinition.isPrimary()) {<br />						primaryCandidate = beanName;<br />					}<br />				}<br />			}<br />			if (autowireCandidates.size() &amp;gt; 0) {<br />				beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);<br />			}<br />		}<br />		if (beanNames.length == 1) {<br />			return getBean(beanNames[0], requiredType);<br />		}<br />		else if(beanNames.length &amp;gt; 1) { // more than one bean defined, lookup primary candidate<br />			if(primaryCandidate != null) {<br />				return getBean(primaryCandidate, requiredType);<br />			}<br />			throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +<br />					beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));<br />		}<br />		else if (beanNames.length == 0 &amp;amp;&amp;amp; getParentBeanFactory() != null) {<br />			return getParentBeanFactory().getBean(requiredType);<br />		}<br />		else  {<br />			throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +<br />					beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));<br />		}<br />	}<br />}<br />

Extended ClassPathXmlApplicationContext

<br />public class PrimaryResolverClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {<br /><br />    public PrimaryResolverClassPathXmlApplicationContext(String... configLocations)<br />        throws BeansException {<br />        super(configLocations);<br />    }<br /><br />    // add more needed constructors, override constructors from superclass.<br /><br />    @Override<br />    protected DefaultListableBeanFactory createBeanFactory() {<br />        return new PrimaryResolverListableBeanFactory(getInternalParentBeanFactory());<br />    }<br />}<br />

Context loader for testing which use the PrimaryResolverListableBeanFactory

<br />public class TestGenericXmlContextLoader extends AbstractContextLoader {<br /><br />    @Override<br />    public final ConfigurableApplicationContext loadContext(final String... locations)<br />        throws Exception {<br /><br />        DefaultListableBeanFactory beanFactory = new PrimaryResolverListableBeanFactory();<br />        beanFactory.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());<br />        beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());<br /><br />        final GenericApplicationContext context = new GenericApplicationContext(beanFactory);<br />        createBeanDefinitionReader(context).loadBeanDefinitions(locations);<br />        AnnotationConfigUtils.registerAnnotationConfigProcessors(context);<br />        context.refresh();<br />        context.registerShutdownHook();<br />        return context;<br />    }<br /><br />    private BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {<br />        return new XmlBeanDefinitionReader(context);<br />    }<br /><br />    @Override<br />    public String getResourceSuffix() {<br />        return "-context.xml";<br />    }<br />}<br />

JUnit Test to play with the examples

<br />@RunWith(SpringJUnit4ClassRunner.class)<br />@ContextConfiguration(locations = { TestSupport.SPRING_CONFIG_FILE_PATH })<br />// @ContextConfiguration(locations = { TestSupport.SPRING_CONFIG_FILE_PATH }, loader = TestGenericXmlContextLoader.class )<br />public class PrimarySupportTest<br />    implements ApplicationContextAware {<br /><br />    private BaseService baseService;<br />    private SingleService singleService;<br />    private InheritedService inheritedService;<br />    private ApplicationContext applicationContext;<br /><br />    @Inject<br />    public void setSingleService(SingleService singleService) {<br />        this.singleService = singleService;<br />    }<br /><br />    @Inject<br />    public void setBaseService(BaseService baseService) {<br />        this.baseService = baseService;<br />    }<br /><br />    @Inject<br />    public void setInheritedService(InheritedService inheritedService) {<br />        this.inheritedService = inheritedService;<br />    }<br /><br />    @Override<br />    public void setApplicationContext(ApplicationContext applicationContext)<br />        throws BeansException {<br />        this.applicationContext = applicationContext;<br />    }<br /><br />    @Test<br />    public void testInjectedSingleService() {<br />        Assert.assertNotNull("singleService is null", singleService);<br />        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(), singleService.getClass().getName());<br />    }<br /><br />    @Test<br />    public void testInjectedBaseAndInheritedServices() {<br />        Assert.assertNotNull("baseService is null", baseService);<br />        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), baseService.getClass().getName());<br />        Assert.assertNotNull("inheritedService is null", inheritedService);<br />        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),<br />            inheritedService.getClass().getName());<br />    }<br /><br />    @Test<br />    public void testSingleServiceWithGetBeanClassAndName() {<br />        SingleService contextSingleService = applicationContext.getBean("singleServiceImpl_1", SingleService.class);<br />        Assert.assertNotNull("contextSingleService is null", contextSingleService);<br />        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(),<br />            contextSingleService.getClass().getName());<br /><br />        contextSingleService = applicationContext.getBean("singleServiceImpl_2", SingleService.class);<br />        Assert.assertNotNull("contextSingleService is null", contextSingleService);<br />        Assert.assertEquals("wrong instance", SingleServiceImpl_2.class.getName(),<br />            contextSingleService.getClass().getName());<br />    }<br /><br />    @Test<br />    public void testBaseAndInheritedServiceWithGetBeanClassAndName() {<br />        BaseService contextBaseService = applicationContext.getBean("baseServiceImpl", BaseService.class);<br />        Assert.assertNotNull("contextBaseService is null", contextBaseService);<br />        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), contextBaseService.getClass().getName());<br /><br />        InheritedService inheritedContextService =<br />            applicationContext.getBean("inheritedServiceImpl", InheritedService.class);<br />        Assert.assertNotNull("inheritedContextService is null", inheritedContextService);<br />        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),<br />            inheritedContextService.getClass().getName());<br />    }<br /><br />    // works with loader = TestGenericXmlContextLoader.class<br />    @Test<br />    public void testSingleServiceWithGetBeanClass() {<br />        // SingleServiceImpl_1 is annotated with @Primary so SingleServiceImpl_1<br />        // instance should returned here<br />        SingleService contextSingleService = applicationContext.getBean(SingleService.class);<br />        Assert.assertNotNull("contextSingleService is null", contextSingleService);<br />        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(),<br />            contextSingleService.getClass().getName());<br />    }<br /><br />    // works with loader = TestGenericXmlContextLoader.class<br />    @Test<br />    public void testServiceWithGetBeanClass() {<br />        // BaseServiceImpl is annotated with @Primary so BaseServiceImpl<br />        // instance should returned here<br />        BaseService contextBaseService = applicationContext.getBean(BaseService.class);<br />        Assert.assertNotNull("contextBaseService is null", contextBaseService);<br />        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), contextBaseService.getClass().getName());<br /><br />        InheritedService inheritedContextService = applicationContext.getBean(InheritedService.class);<br />        Assert.assertNotNull("inheritedContextService is null", inheritedContextService);<br />        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),<br />            inheritedContextService.getClass().getName());<br />    }<br />}<br />

One thought on “Spring – Usage of @Primary annotation or attribut for inject default bean instances

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>