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

public interface SingleService {
	void callMe();
}

@Named
public class SingleServiceImpl_1 implements SingleService {
	@Override
	public void callMe() {
	}
}

@Named
public class SingleServiceImpl_2 implements SingleService {
	@Override
	public void callMe() {
	}
}

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

@Inject
public void setSingleService(SingleService singleService) {
	this.singleService = singleService;
}

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.

@Named
@Primary
public class SingleServiceImpl_1 implements SingleService {
	@Override
	public void callMe() {
	}
}

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.

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

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.

public interface BaseService {
	void callBase();
}

public interface InheritedService extends BaseService {
	void callInherited();
}

@Named
public class BaseServiceImpl implements BaseService {

	@Override
	public void callBase() {
	}
}

@Service
public class InheritedServiceImpl
	extends BaseServiceImpl implements InheritedService {

	@Override
	public void callInherited() {
	}
}

The code for inject the instances:

@Inject
public void setBaseService(BaseService baseService) {
    this.baseService = baseService;
}

@Inject
public void setInheritedService(InheritedService inheritedService) {
    this.inheritedService = inheritedService;
}

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.

@Named
@Primary
public class BaseServiceImpl implements BaseService {

	@Override
	public void callBase() {
	}
}

or with @Named

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

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.

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

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

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

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

public class PrimaryResolverListableBeanFactory extends
		DefaultListableBeanFactory {

	public PrimaryResolverListableBeanFactory() {
		super();
	}

	public PrimaryResolverListableBeanFactory(BeanFactory parentBeanFactory) {
		super(parentBeanFactory);
	}

	@Override
	public  T getBean(Class requiredType) throws BeansException {
		Assert.notNull(requiredType, "Required type must not be null");
		String[] beanNames = getBeanNamesForType(requiredType);
		String primaryCandidate = null;
		if (beanNames.length > 1) {
			ArrayList autowireCandidates = new ArrayList();

			for (String beanName : beanNames) {
				BeanDefinition beanDefinition = getBeanDefinition(beanName);
				if (beanDefinition.isAutowireCandidate()) {
					autowireCandidates.add(beanName);
					if(beanDefinition.isPrimary()) {
						primaryCandidate = beanName;
					}
				}
			}
			if (autowireCandidates.size() > 0) {
				beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
			}
		}
		if (beanNames.length == 1) {
			return getBean(beanNames[0], requiredType);
		}
		else if(beanNames.length > 1) { // more than one bean defined, lookup primary candidate
			if(primaryCandidate != null) {
				return getBean(primaryCandidate, requiredType);
			}
			throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +
					beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		else if (beanNames.length == 0 && getParentBeanFactory() != null) {
			return getParentBeanFactory().getBean(requiredType);
		}
		else  {
			throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +
					beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));
		}
	}
}

Extended ClassPathXmlApplicationContext

public class PrimaryResolverClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {

    public PrimaryResolverClassPathXmlApplicationContext(String... configLocations)
        throws BeansException {
        super(configLocations);
    }

    // add more needed constructors, override constructors from superclass.

    @Override
    protected DefaultListableBeanFactory createBeanFactory() {
        return new PrimaryResolverListableBeanFactory(getInternalParentBeanFactory());
    }
}

Context loader for testing which use the PrimaryResolverListableBeanFactory

public class TestGenericXmlContextLoader extends AbstractContextLoader {

    @Override
    public final ConfigurableApplicationContext loadContext(final String... locations)
        throws Exception {

        DefaultListableBeanFactory beanFactory = new PrimaryResolverListableBeanFactory();
        beanFactory.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
        beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());

        final GenericApplicationContext context = new GenericApplicationContext(beanFactory);
        createBeanDefinitionReader(context).loadBeanDefinitions(locations);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
        context.refresh();
        context.registerShutdownHook();
        return context;
    }

    private BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {
        return new XmlBeanDefinitionReader(context);
    }

    @Override
    public String getResourceSuffix() {
        return "-context.xml";
    }
}

JUnit Test to play with the examples

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { TestSupport.SPRING_CONFIG_FILE_PATH })
// @ContextConfiguration(locations = { TestSupport.SPRING_CONFIG_FILE_PATH }, loader = TestGenericXmlContextLoader.class )
public class PrimarySupportTest
    implements ApplicationContextAware {

    private BaseService baseService;
    private SingleService singleService;
    private InheritedService inheritedService;
    private ApplicationContext applicationContext;

    @Inject
    public void setSingleService(SingleService singleService) {
        this.singleService = singleService;
    }

    @Inject
    public void setBaseService(BaseService baseService) {
        this.baseService = baseService;
    }

    @Inject
    public void setInheritedService(InheritedService inheritedService) {
        this.inheritedService = inheritedService;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Test
    public void testInjectedSingleService() {
        Assert.assertNotNull("singleService is null", singleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(), singleService.getClass().getName());
    }

    @Test
    public void testInjectedBaseAndInheritedServices() {
        Assert.assertNotNull("baseService is null", baseService);
        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), baseService.getClass().getName());
        Assert.assertNotNull("inheritedService is null", inheritedService);
        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),
            inheritedService.getClass().getName());
    }

    @Test
    public void testSingleServiceWithGetBeanClassAndName() {
        SingleService contextSingleService = applicationContext.getBean("singleServiceImpl_1", SingleService.class);
        Assert.assertNotNull("contextSingleService is null", contextSingleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(),
            contextSingleService.getClass().getName());

        contextSingleService = applicationContext.getBean("singleServiceImpl_2", SingleService.class);
        Assert.assertNotNull("contextSingleService is null", contextSingleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_2.class.getName(),
            contextSingleService.getClass().getName());
    }

    @Test
    public void testBaseAndInheritedServiceWithGetBeanClassAndName() {
        BaseService contextBaseService = applicationContext.getBean("baseServiceImpl", BaseService.class);
        Assert.assertNotNull("contextBaseService is null", contextBaseService);
        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), contextBaseService.getClass().getName());

        InheritedService inheritedContextService =
            applicationContext.getBean("inheritedServiceImpl", InheritedService.class);
        Assert.assertNotNull("inheritedContextService is null", inheritedContextService);
        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),
            inheritedContextService.getClass().getName());
    }

    // works with loader = TestGenericXmlContextLoader.class
    @Test
    public void testSingleServiceWithGetBeanClass() {
        // SingleServiceImpl_1 is annotated with @Primary so SingleServiceImpl_1
        // instance should returned here
        SingleService contextSingleService = applicationContext.getBean(SingleService.class);
        Assert.assertNotNull("contextSingleService is null", contextSingleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(),
            contextSingleService.getClass().getName());
    }

    // works with loader = TestGenericXmlContextLoader.class
    @Test
    public void testServiceWithGetBeanClass() {
        // BaseServiceImpl is annotated with @Primary so BaseServiceImpl
        // instance should returned here
        BaseService contextBaseService = applicationContext.getBean(BaseService.class);
        Assert.assertNotNull("contextBaseService is null", contextBaseService);
        Assert.assertEquals("wrong instance", BaseServiceImpl.class.getName(), contextBaseService.getClass().getName());

        InheritedService inheritedContextService = applicationContext.getBean(InheritedService.class);
        Assert.assertNotNull("inheritedContextService is null", inheritedContextService);
        Assert.assertEquals("wrong instance", InheritedServiceImpl.class.getName(),
            inheritedContextService.getClass().getName());
    }
}

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

  1. Corrado

    Hi, this information is great and helped me figure out a couple of problems with autowiring. Can you please fix the rendering of the code snippets? They are super hard to read, but they are very good examples.

    Thanks.

    Reply

Leave a Reply to Daniel Nydegger Cancel reply

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