Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring overriding primary bean with non-primary bean

Tags:

I am trying to override a Spring bean during a test declared in a test configuration with the use of @Primary. One declaration is in the src/main/java path, the other, the primary, is in src/test/java path.

However, Spring is intentionally replacing the primary bean with the the non-primary bean, the one I don't want to use for the test. If I simply comment out the production (src/main/java) configuration bean, it uses the primary test (src/main/test) bean in the test configuration as desired. (Clearly I can't comment out code every time I want to run a test.)

From the logs:

o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'sqsConnectionFactory' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=testJmsConfiguration; factoryMethodName=sqsConnectionFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/foo/configuration/TestJmsConfiguration.class]]

with

[Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=jmsConfiguration; factoryMethodName=sqsConnectionFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/foo/configuration/JmsConfiguration.class]]

Why is spring replacing a primary bean with a non-primary bean and how do I get Spring to use the bean specifically marked as the primary bean?

Edit: The src/main/java configuration:

@Configuration public class JmsConfiguration {  ... other bean declarations here ...  @Bean public SQSConnectionFactory sqsConnectionFactory(Region region) throws JMSException {     return SQSConnectionFactory.builder()             .withRegion(region)             .build(); } } 

The test configuration:

@Configuration public class TestJmsConfiguration {  @Bean(name="messageProducerMock") public MessageProducer mockMessageProducer() {     return new MessageProducerMock(); }  ... other bean declarations here ...  @Bean @Primary public SQSConnectionFactory sqsConnectionFactory(@Qualifier("messageProducerMock") MessageProducer messageProducerMock) throws JMSException {     ... returning setup mock here } } 

The class with the tests is annotated with:

@RunWith(SpringRunner.class) @SpringBootTest @ActiveProfiles(profiles = {"test"}) 
like image 523
FiguringThisOut Avatar asked Mar 02 '17 19:03

FiguringThisOut


People also ask

Can we override the bean in spring boot?

Bean Overriding Spring beans are identified by their names within an ApplicationContext. Therefore, bean overriding is a default behavior that happens when we define a bean within an ApplicationContext that has the same name as another bean. It works by simply replacing the former bean in case of a name conflict.

How do you override a bean in spring?

The most common approach followed for overriding a spring bean is to define a new bean, with the same id as the original bean, in a separate XML file. During context initialization, Spring would register the last bean found for the id, and use it for all the injections.

Can we use @bean without @configuration?

@Bean methods may also be declared within classes that are not annotated with @Configuration. For example, bean methods may be declared in a @Component class or even in a plain old class. In such cases, a @Bean method will get processed in a so-called 'lite' mode.

Can @bean method be private?

Any @Bean annotated method, which is not public (i.e. with protected, private and default visibility), will create a 'hidden' bean. In the example above, mainBean has been configured with both publicBean and hiddenBean.


2 Answers

@Primary takes effect only at injection point, when there is a conflict because different beans match the condition to be injected, and a decision needs to be made.

@Primary is not used at beans initialisation. As you are using two different methods creating the same bean, and you are not naming any of them Spring considers you are trying to override it, so this behaviour can happen. Given a name is the easiest solution, but bear in mind that your context will still be initialising the bean you do not want use.

like image 71
alfcope Avatar answered Sep 23 '22 09:09

alfcope


I think you might be missing @ContextConfiguration in your test class.

Example of test configuration class (src/test/java/TestConfiguration.class):

@Configuration @ComponentScan public class TestConfiguration {     @Bean     RabbitSender rabbitSender() {         return mock(RabbitSender.class);     }  } 

Example of test class:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TestConfiguration.class) public class SomeServiceTest {  } 
like image 22
hya Avatar answered Sep 22 '22 09:09

hya