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()); } }
Visit spring-practice project at my github account to download the code.
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.
Ops…, thank you for pointing out that. Now it is fixed.