Spring - 激活多个配置文件

53

我在Spring中有一个bean,我希望只有当2个配置文件都被激活时才启用它。基本上,它应该是这样的:

@Profile({"Tomcat", "Linux"})
public class AppConfigMongodbLinux{...}

@Profile({"Tomcat", "WindowsLocal"})
public class AppConfigMongodbWindowsLocal{...}

当我使用-Dspring.profiles.active=Tomcat,WindowsLocal时,我希望它只尝试使用AppConfigMongodbWindowsLocal,但它仍然尝试注册AppConfigMongodbLinux

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appConfigMongodbLinux': Injection of autowired dependencies failed

当两个配置文件都处于激活状态时,使bean只能被注册,这是否可能?还是我使用方法有误? :)

谢谢!!


编辑:发布完整堆栈。

实际上出错的是一个属性在属性文件中缺失,但是这个bean会被激活吗?我想了解这一点,以确保我没有激活错误的bean。

org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
    ...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appConfigMongodbLinux': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.Integer mycompany.config.AppConfigMongodbLinux.mongoPort; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'mongo.port' in string value "${mongo.port}"
    ... 40 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.Integer mycompany.config.AppConfigMongodbLinux.mongoPort; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'mongo.port' in string value "${mongo.port}"
    ...
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'mongo.port' in string value "${mongo.port}"

1
这似乎不是与配置文件有关的问题。请检查appConfigMongodbLinux中自动装配的bean是否可用。发布完整的堆栈跟踪。 - Shankar
1
@ShankarPS 看起来是配置文件的问题。一个他不想激活的配置文件正在寻找该配置中不存在的属性。 - chrylis -cautiouslyoptimistic-
5个回答

45

Spring 5.1及以上版本提供了更多功能,用于指定更复杂的配置文件字符串表达式。在您的情况下,可以通过以下方式实现所需的功能:

@Profile({"Tomcat & Linux"})
@Configuration
public class AppConfigMongodbLinux {...}

请阅读Spring参考文档中的使用@Profile章节以获取更多信息。
更新(方法级别的配置文件表达式): 实际上,我已经测试了一些@Bean方法级别的配置文件表达式,一切都像魔法一样顺利运行:
/**
 * Shows basic usage of {@link Profile} annotations applied on method level.
 */
@Configuration
public class MethodLevelProfileConfiguration {

    /**
     * Point in time related to application startup.
     */
    @Profile("qa")
    @Bean
    public Instant startupInstant() {
        return Instant.now();
    }

    /**
     * Point in time related to scheduled shutdown of the application.
     */
    @Bean
    public Instant shutdownInstant() {
        return Instant.MAX;
    }

    /**
     * Point in time of 1970 year.
     */
    @Profile("develop & production")
    @Bean
    public Instant epochInstant() {
        return Instant.EPOCH;
    }
}

集成测试:

/**
 * Verifies {@link Profile} annotation functionality applied on method-level.
 */
public class MethodLevelProfileConfigurationTest {

    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes = MethodLevelProfileConfiguration.class)
    @ActiveProfiles(profiles = "qa")
    public static class QaActiveProfileTest {

        @Autowired
        private ApplicationContext context;

        @Test
        public void shouldRegisterStartupAndShutdownInstants() {
            context.getBean("startupInstant", Instant.class);
            context.getBean("shutdownInstant", Instant.class);

            try {
                context.getBean("epochInstant", Instant.class);
                fail();
            } catch (NoSuchBeanDefinitionException ex) {
                // Legal to ignore.
            }
        }
    }

    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes = MethodLevelProfileConfiguration.class)
    @ActiveProfiles(profiles = {"develop", "production"})
    public static class MethodProfileExpressionTest {

        @Autowired
        private ApplicationContext context;

        @Test
        public void shouldRegisterShutdownAndEpochInstants() {
            context.getBean("epochInstant", Instant.class);
            context.getBean("shutdownInstant", Instant.class);

            try {
                context.getBean("startupInstant", Instant.class);
                fail();
            } catch (NoSuchBeanDefinitionException ex) {
                // Legal to ignore.
            }
        }
    }
}

已测试Spring 5.1.2版本。


1
似乎它只在配置(类)级别上起作用。我在方法级别上使用了这个语法进行了一些测试,但在Spring 5.1上它并没有遵守条件。 - agodinhost
1
@agodinhost,您能否请看一下更新部分? - Laplas
1
似乎我使用了错误的语法 - 没有花括号 - 我不记得了。实际上,我正在使用Spring Boot 2.1.1.RELEASE - 不是我说的Spring 5.1。这些花括号很烦人,但它们是有意义的 - 这是一个表达式,有点短,但是确实是一个表达式,对吧? - agodinhost
1
@agodinhost 这是一个内联数组,遵循标准的Java语法(例如,请查看链接)。 顺便说一下,Spring Boot 2.1.1 RELEASE使用Spring 5.1.3。 - Laplas
1
@Profile({"Tomcat", "Linux"}) 的意思是其中一个 (OR)。 - Sarvar Nishonboyev
显示剩余2条评论

26

不幸的是,只要任何一个列出的配置文件处于活动状态,@Profile注解就会被激活。有几种方法可以解决这个问题。

  • 将常见的@Profile("Tomcat")注解应用于顶级配置类,然后将@Profile("Windows")应用于嵌套配置类(或@Bean方法)。
  • 如果可以接受Spring Boot作为依赖项,请使用@AllNestedConditions创建一个AND而不是OR的注解。

如果您正在使用Spring Boot自动配置类,看起来您尝试执行的操作将非常容易实现;如果在应用程序生命周期的此阶段引入自动配置是可行的,我建议考虑使用它。


2
或者一个条件实现,例如 https://gist.github.com/zapl/1262d21b8866ebc61e48ece505277f69,派生自 @Profile - zapl
我确实使用SpringBoot!我会看看你提到的注释,但第一种选项也很有意义。谢谢!! - Felipe S.

14

如果只有多个配置文件中的任何一个存在时才加载配置,请使用以下所示的@Profile注释:

@Profile({ "production", "preproduction" })
@Configuration
public class MyConfigClass {
    // code
}

如果只有当多个配置文件全部存在时才加载配置,可以使用如下的@Profile注解:

@Profile({ "production & preproduction" })
@Configuration
public class MyConfigClass {
    // code
}

为了避免在存在某些特定配置文件(profiles)时加载配置信息,请按照以下示例使用@Profile注解:不要加载任何特定配置文件(profiles)。
@Profile({ "!production & !preproduction" })
@Configuration
public class MyConfigClass {
    // code
}

5
@ConditionalOnExpression("#{environment.acceptsProfiles('Tomcat') && environment.acceptsProfiles('Linux')}")

致谢: Spring源代码。使用IDE查找@ConditionalOnExpression,然后“查找用法”以查看源代码中的相关示例。这将帮助您成为更好的开发人员。


它是否接受!(NOT)运算符? - c.sankhala

0

第一个配置文件位于顶层。 我是这样检查第二个配置文件的:

@Autowired
private Environment env;
...
final boolean isMyProfile = Arrays.stream(env.getActiveProfiles()).anyMatch("MyProfile"::equals);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接