7

I've got two beans. Both implement the mailing function. One is only working when it is deployed to an application server. The other one is used for testing.

We have profile for each developer and environment. I want to wire the testing bean only when actually testing. The other bean should be used when not testing. How can I archive this?

@Component
@Profile("localtest")
public class OfflineMail implements Mailing {}

Solution approaches:

Using "default" I read this somewhere, but there seems to be no fall-back to "default" for a profile like "dev":

@Component
@Profile("default")
public class OnlineMail implements Mailing {}

-> Exception for no bean for wiring found.

Leaving the profile out:

@Component
public class OnlineMail implements Mailing {}

-> Throws a unique exception when running the "localtest" profile.

Adding all profiles:

@Component
@Profile("prod")
@Profile("integration")
@Profile("test")
@Profile("dev1")
@Profile("dev2")
@Profile("dev3")
...
public class OnlineMail implements Mailing {}

This is actually working, however our devs aren't numbered they use "dev<WindowsLogin>" and adding the profiles, may work for one bean, but one will get into trouble when using it for several beans as this definitely gets ugly.

Using something like @Profile("!localtest") doesn't seem to work as well.

Does anyone know a nicer way to get a "wire by default if no specific bean is found"?

Udo Held
  • 12,314
  • 11
  • 67
  • 93
  • `@Component` without `@Profile` has to be default bean, if nothing is wired. Have you tried to avoid unique exception with setting name for component? I mean, `@Component("mail")` with `@Profile("localtest")` and without it? – n1ckolas Mar 05 '13 at 14:04

3 Answers3

10

I finally found an easy solution.

The online mail is just wired by default.

@Component
public class OnlineMail implements Mailing {}

Using the @Primary annotation the offline mail takes precedence over the OnlineMail and avoids the Unique exception.

@Component
@Profile("localtest")
@Primary
public class OfflineMail implements Mailing {}
Udo Held
  • 12,314
  • 11
  • 67
  • 93
2

Try this:

@Component
@Profile("production")
public class OnlineMail implements Mailing {}

@Component
@Profile("localtest")
public class OfflineMail implements Mailing {}

Then run tests using @ActiveProfiles("localtest") and run production enviroment using "production" as DEFAULT profile.

Also I hope in next version of Spring ActiveProfilesResolver will be introduced SPR-10338 - it may be helpfull for you (to avoid "dev1", "dev2" and so on).

Michail Nikolaev
  • 3,733
  • 22
  • 18
  • I don't get the point for this solution. We are setting the correct environment everywhere where we deploy the application. So Spring shouldn't load the default, should it? And I'm not sure on how to separate default for testing as the production resource will be in our test file as well. – Udo Held Mar 05 '13 at 14:37
  • There are two type of profiles - default< profiles and active profiles, check http://stackoverflow.com/questions/10041410/default-profile-in-spring-3-1 – Michail Nikolaev Mar 05 '13 at 14:50
  • So, it is better to avoid use default (empty) spring profile for any bean which already has mock - use "production" and "localtest" profiles. And on start select required - using @ActiveProfiles for test and 'pring.profiles.default' or'pring.profiles.active' for server – Michail Nikolaev Mar 05 '13 at 14:53
  • Found a better solution. The "default" behavior just works out of the box and the test one uses `@Primary` for getting precedence. Thanks for the ideas. I was already using `@ActiveProfiles` for testing. – Udo Held Mar 10 '13 at 11:11
0

Spring supports inject the Bean by @Profile very well:

interface Talkative {
    String talk();
}

@Component
@Profile("dev")
class Cat implements Talkative {
        public String talk() {
        return "Meow.";
    }
}

@Component
@Profile("prod")
class Dog implements Talkative {
    public String talk() {
        return "Woof!";
    }
}

Works in unit test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContex-test.xml"})
@ActiveProfiles(value = "dev")
public class InjectByDevProfileTest
{
    @Autowired
    Talkative talkative;

    @Test
    public void TestTalkative() {
        String result = talkative.talk();
        Assert.assertEquals("Meow.", result);

    }
}

Works in Main():

@Component public class Main {

        public static void main(String[] args) {
            // Enable a "dev" profile
            System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
            ApplicationContext context =
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            Main p = context.getBean(Main.class);
            p.start(args);
        }

        @Autowired
        private Talkative talkative;

        private void start(String[] args) {
            System.out.println(talkative.talk());
        }
    }

Check this for the Demo code: https://github.com/m2land/InjectByProfile

Nicholas Lu
  • 1,655
  • 15
  • 14