6

I have an abstract Spring configuration class that includes a common bean:

public abstract class AbstractConfig {
    @Bean
    public CommonBean commonBean {
        CommonBean commonBean = new CommonBean();
        commonBean.specifics = getSpecifics();
    };

    public abstract String getSpecifics();
}

The common bean's specifics are set by its subclasses:

package realPackage;
@Configuration
public class RealConfig extends AbstractConfig {
    @Override
    public String getSpecifics() {
        return "real";
    }
}

...and...

package testPackage;
@Configuration
@ComponentScan(basePackages = {"testPackage", "realPackage" })
public class TestConfig extends AbstractConfig {
    @Override
    public String getSpecifics() {
        return "test";
    }
}

My test only needs to use the TestConfig and shouldn't know about the RealConfig (but does need access to other components in realPackage). It begins:

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

With the code above, this works as expected and uses the "test" specifics. But if I reverse the order of packages in the @ComponentScan, the "real" specifics are used instead. This is baffling me - am specifying the TestConfig so surely it should be calling the overridden method on this? Could anyone advise as to the reasons Spring does this and how it might be resolved?

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208

2 Answers2

1

Your current example will most likely not compile. AbstractConfig should look like this code snippet, but I assume this is only a copy/paste error.

public abstract class AbstractConfig {
    @Bean
    public CommonBean commonBean() {
        CommonBean commonBean = new CommonBean();
        commonBean.specifics = getSpecifics();
        return commonBean;
    }

    public abstract String getSpecifics();
}

The feature you are looking for is the @Profile annotation (see https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile for more details). You need to change your code as follows

RealConfig

package realPackage;
@Configuration
@Profile("real")
public class RealConfig extends AbstractConfig {
...

TestConfig

package testPackage;
@Configuration
@ComponentScan(basePackages = {"testPackage", "realPackage" })
@Profile("test")
public class TestConfig extends AbstractConfig {

MyIntegrationTestIT

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@ActiveProfiles("test")
public class MyIntegrationTestIT {
...
wegenmic
  • 320
  • 1
  • 3
  • 13
0

The issue turned out to be due to a lack of understanding on my part of how beans annotated with @Configuration are picked up by a component scan.

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • 1
    thanks. would appreciate a little more elaboration in this specific context. – msanjay Sep 12 '19 at 09:31
  • Since @Configuration is meta annotated with @Component, it too becomes eligible for component scanning and gets picked up. Therefore the `RealConfig` configuration bean gets picked up by the component scan on `TestConfig` class. More information can be found in the Spring framework docs: https://docs.spring.io/spring/docs/4.3.26.RELEASE/spring-framework-reference/htmlsingle/#beans-java-configuration-annotation – sidmishraw Jan 31 '20 at 02:06