Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Primary and primary attribute of <bean> element are not considered for calls to getBean(Class) [SPR-7854] #12511

Closed
spring-issuemaster opened this issue Dec 30, 2010 · 14 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Dec 30, 2010

Daniel Nydegger opened SPR-7854 and commented

If a bean is annotated with @Primary or the attribute primary is set on the <bean> element, then the injection mechanism choose the primary configured bean as first choice if more than one candidate are qualified. This works fine with usage of @Autowired or @Inject.

By resolve the bean over the application context, I expected the same behaviour. Because the DefaultListableBeanFactory implementation of <T> T getBean(Class<T> requiredType) method doesn't check the the primary attribute of BeanDefinition, a NoSuchBeanDefinitionException is thrown in case of multiple qualified bean candidates regardless the the primary qualification.

I posted a workaround solution in my blog: http://develop.nydi.ch/2010/12/spring-primary-bean-injection/

To reproduce this behavior you can run the following JUnit test case.

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

	private SingleService singleService;
	private ApplicationContext applicationContext;
	
	@Inject
    public void setSingleService(SingleService singleService) {
		this.singleService = singleService;
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}
    
    @Test
    public void testInjectedService() {
        Assert.assertNotNull("contextSingleService is null", singleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(), singleService.getClass().getName());
    }
    
    @Test
    public void testServiceWithGetBean() {
    	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());

        // SingleServiceImpl_1 is annotated with @Primary so SingleServiceImpl_1 instance should returned here, but 
    	contextSingleService = applicationContext.getBean(SingleService.class);
        Assert.assertNotNull("contextSingleService is null", contextSingleService);
        Assert.assertEquals("wrong instance", SingleServiceImpl_1.class.getName(), contextSingleService.getClass().getName());
    }
}
public interface BaseService {
	void callBase();
}
@Service
@Named
@Primary
public class SingleServiceImpl_1 implements SingleService {
	@Override
	public void callMe() {
	}
}
@Service
@Named
public class SingleServiceImpl_2 implements SingleService {
	@Override
	public void callMe() {
	}
}

Affects: 3.0.5

Issue Links:

  • #13135 Make BeanFactoryLocator aware of autowiring (and primary flag) ("is duplicated by")
  • #12590 Add support for type-based RuntimeBeanReference

Referenced from: commits 7e74fd2

12 votes, 13 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 30, 2010

Daniel Nydegger commented

formated version of code...

Test

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

    private SingleService singleService;
    private ApplicationContext applicationContext;

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

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

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

    @Test
    public void testServiceWithGetBean() {
        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());

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

Services

public interface SingleService {
	void callMe();
}

@Named
@Primary
public class SingleServiceImpl_1
    implements SingleService {

    @Override
    public void callMe() {
    }
}

@Named
public class SingleServiceImpl_2
    implements SingleService {

    @Override
    public void callMe() {
    }
}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 3, 2011

Oliver Drotbohm commented

Added formatting

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 19, 2011

Jan Haegeman commented

We have run into this issue as well. Because of dynamic-proxy-based AOP and wanting to retrieve beans by their type, we have to retrieve the beans by their interface (and not the actual implementation class). However, when there are several implementations of the same interface, we would like to use also the @Primary annotation, not only for auto-wiring, but also for the getBean() methods...

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Feb 3, 2011

Juergen Hoeller commented

This is a known limitation at this point. We'll revisit it for Spring 3.1.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 4, 2012

Nick Williams commented

It's been 15 months since any comments were attached to this bug. This seems like a rather big oversight to go unfixed for so long. Why wasn't this fixed in 3.1? Can we PLEASE get this fixed in 3.1.2 (or, if there isn't going to be a 3.1.2, in 3.2.0-M2?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 18, 2013

Dan Lipofsky commented

I'd really like to see this fixed. I just encountered it and can verify it still exists in 3.2.0.RELEASE.

I noticed that if you have 2 beans that match the class, and they have identical IDs, and only one is marked as primary, then applicationContext.getBean(someClass) works. However if they have different IDs you get

NoSuchBeanDefinitionException: No unique bean of type [...] is defined: expected single bean but found 2: Bean1,Bean2
This really shouldn't depend on ID since we are just trying to get by class.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 22, 2013

Phil Webb commented

commit 7e74fd2b7fb30e6206d40bb7235d82433682386a
Author: Phillip Webb <pwebb@vmware.com>
Commit: Phillip Webb <pwebb@vmware.com>

    Consider primary attribute with getBean(Class)
    
    Update DefaultListableBeanFactory.getBean(Class<?> beanClass) to
    consider the 'primary' attribute of bean definitions. This makes
    getBean() behave in the same way as autowiring.
    
    Issue: SPR-7854
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Nick Williams commented

Woohoo! Thank you! Now I can't wait until 3.2.1 comes out, so that I can remove all of this hack code! =D

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Chris Beams commented

Hi Nick, we plan to release 3.2.1 tomorrow. We'd love it if you grab a snapshot in the meantime and let us know that the new support works as expected for you.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Nick Williams commented

Absolutely. Especially after the trouble I gave y'all on #14428. :-P

I'll try to get that done this afternoon and let you know.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Nick Williams commented

I'll have to amend my earlier statement. Wish I'd looked into it more, then. I do not have the Git/Gradle knowledge necessary to download, compile and build from your source repository. I am an Ant/Maven/Subversion kind of guy.

However, if somebody has a snapshot already built following this bug fix and can point me to the JAR files, I'll gladly download them.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Phil Webb commented

Hi Nick,

Snapshot builds are available from http://repo.springsource.org/snapshot, there is some more information on the WIKI (https://github.com/SpringSource/spring-framework/wiki/SpringSource-repository-FAQ).

If you are using maven you should be able to simply add the repo and change the version to 3.2.1.BUILD-SNAPSHOT

Cheers,
Phil.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Nick Williams commented

Ahhh. That's what I was looking for.

I do use Maven, but the particular project that needs this bug fixed does NOT use Maven, so I have to manually download and replace them. I'll let you know what I discover.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 23, 2013

Nick Williams commented

Confirmed! The bug is fixed! =D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.