Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Primary spring bean overridden by ImportResource in Configuration

I have a Spring test-configuration class which is supposed to override an existing in bean in xml-config. But my problem is that the xml bean overrides the bean annotated with primary in my test-config. I have tried naming the test-bean with a different name but that has not worked for me either.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {CamelJmsTest.TestConfig.class})
public class CamelJmsTest {

    @Configuration
    @ImportResource("classpath:production-beans-camel-jms.xml")
    public static class TestConfig {

    @Primary
    @Bean
    public JmsTemplate jmsTemplate() {
        return new JmsTemplate(new ActiveMQConnectionFactory("", "", ACTIVE_MQ_HOST));
    }

    @Primary
    @Bean // ideally i just want this bean to override the bean imported from the xml config
    public RouteConfigBuilder routeConfig() {
        return RouteConfigBuilder.builder().autoStart(true).build();
    }

    @Primary
    @Bean
    public RouteBuilder routeBuilder(@Value("${amq.endpoint}") String endpoint,
                                                              @Autowired Processor processor) {
        return new RouteBuilder(routeConfig(), "", endpoint, processor);
    }
}

    private static final String ACTIVE_MQ_HOST = "vm://activeMQ";

  @BeforeClass
  public static void setActiveMQ() {
    System.setProperty("amq.endpoint", ACTIVE_MQ_HOST);
  } 

  @Autowired
  JmsTemplate jmsTemplate;
  @Test
  public void postJmsMessage() {

    jmsTemplate.send("queue/test", new MessageCreator() {
        @Override
        public Message createMessage(Session session) throws 
     JMSException {
            return session.createTextMessage("Hello World");
        }
    });

    try {
        for (int i = 0; i < 100; i++) {
            Thread.sleep(100);
        }
    } catch (Exception ignored) {
    }
  }
}

Here is the relevant xml config:

<bean id="routeConfig" class="routing.RouteConfigBuilder" init-method="builder">
    <constructor-arg name="redeliveryDelay" value="${<props>.redeliveryDelay}" />
    <constructor-arg name="maximumRedeliveries" value="${<props>.maximumRedeliveries}" />
    <constructor-arg name="autoStart" value="false" />
</bean>

<bean id="routeBuilder" class="routing.RouteBuilder">
    <constructor-arg ref="routeConfig" />
    <constructor-arg name="routeId" value="ROUTE_ID_1"/>
    <constructor-arg name="endpoint" value="${amq.endpoint}" />
    <constructor-arg name="processor" ref="myProcessor" />
</bean>    

This is the log output. When running the test:

Overriding bean definition for bean 'routeConfig' with a different 
definition:
  replacing [Root bean: class [null]; scope=; abstract=false; 
  lazyInit=false; autowireMode=3; dependencyCheck=0; 
  autowireCandidate=true; primary=true;
  factoryBeanName=CamelJmsTest.TestConfig; 
  factoryMethodName=routeConfig; initMethodName=null; 
  destroyMethodName=(inferred);
defined in CamelJmsTest]
with [Generic bean: class [RouteConfigBuilder]; scope=; 
 abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0;
  autowireCandidate=true; primary=false; factoryBeanName=null; 
  factoryMethodName=null; initMethodName=builder; 
  destroyMethodName=null;
defined in class path resource [production-beans-camel-jms.xml.xml]]

As you can see my primary bean in the test-configuration is overridden and I want to use my test-config bean in the test.

I am using Spring 4.3.x.

I've read a lot of related posts, but none of the gave me the answer. Any help would be appreciated.

like image 828
thomas77 Avatar asked Dec 13 '18 08:12

thomas77


People also ask

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.

What allows us to override already configured beans and to substitute them with different object definition?

Bean aliasing allows us to override already configured beans and to substitute them with a different object definition. This is most useful when the bean definitions are inherited from an external resource, which is out of our control. In the following example, I will show you how bean aliasing works.

Can we override bean in Spring?

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.

What is @configuration and @bean in Spring?

Spring @Configuration annotation is part of the spring core framework. Spring Configuration annotation indicates that the class has @Bean definition methods. So Spring container can process the class and generate Spring Beans to be used in the application.


1 Answers

The name change appeared to work my simplified version.

I don't think it matters but the construction style of RouteConfigBuilder and RouteBuilder appears different between the Java and the XML.

Tested with 4.3.20

package com.stackoverflow.q53757986;

import static org.junit.Assert.*;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;

@SuppressWarnings("javadoc")
public class CamelJmsTest {

    static final String ACTIVE_MQ_HOST = "vm://activeMQ?broker.persistent=false";

    /** The Constant SPRING_CLASS_RULE. */
    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    /** The spring method rule. */
    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @BeforeClass
    public static void setActiveMQ() {
        System.setProperty("amq.endpoint", ACTIVE_MQ_HOST);
    }

    @Autowired
    JmsTemplate jmsTemplate;

    @Autowired
    RouteConfigBuilder routeConfig;

    @Autowired
    RouteBuilder routeBuilder;

    @Test
    public void postJmsMessage() {

        assertEquals("java", this.routeConfig.source);
        assertEquals("java", this.routeBuilder.source);

        this.jmsTemplate.send("queue/test", (MessageCreator) session -> session.createTextMessage("Hello World"));

        try {
            for (int i = 0; i < 100; i++) {
                Thread.sleep(100);
            }
        } catch (Exception ignored) {}
    }

    @Configuration
    @ImportResource("classpath:production-beans-camel-jms.xml")
    static class TestConfig {

        @Primary
        @Bean
        public JmsTemplate jmsTemplate() {
            return new JmsTemplate(new ActiveMQConnectionFactory("", "", ACTIVE_MQ_HOST));
        }

        @Primary
        @Bean // ideally i just want this bean to override the bean imported from the xml config
        public RouteConfigBuilder routeConfig2() {
            return new RouteConfigBuilder("java");
        }

        @Primary
        @Bean
        public RouteBuilder routeBuilder2() {
            return new RouteBuilder(routeConfig2(), "java");
        }
    }
    }
<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

      <bean id="routeConfig"  class="com.stackoverflow.q53757986.RouteConfigBuilder">
        <constructor-arg name="source" value="xml" />
      </bean>

      <bean id="routeBuilder"  class="com.stackoverflow.q53757986.RouteBuilder">
        <constructor-arg name="routeConfig" ref="routeConfig" />
        <constructor-arg name="source" value="xml" />
      </bean>
    </beans>   
like image 167
Jeff Avatar answered Oct 18 '22 10:10

Jeff