47

Spring Profile注解允许您选择配置文件。但是,如果您阅读文档,它只允许您使用 OR 操作选择多个配置文件。如果您指定 @Profile("A", "B") 则如果配置文件 A 或配置文件 B 处于活动状态,那么您的 bean 将启动。

我们的用例不同,我们希望支持多种配置的 TEST 和 PROD 版本。因此,有时我们希望仅当配置文件 TEST 和 CONFIG1 都处于活动状态时才自动装配 bean。

有什么办法可以用 Spring 做到这一点吗?最简单的方法是什么?

4

7 回答 7

37

从 Spring 5.1(并入 Spring Boot 2.1)开始,可以在配置文件字符串注释中使用配置文件表达式。所以:

Spring 5.1 (Spring Boot 2.1) 及更高版本中,它很简单:

@Component
@Profile("TEST & CONFIG1")
public class MyComponent {}

弹簧 4.x 和 5.0.x

  • 方法 1:由@Mithun 回答,它完美地涵盖了您在配置文件注释中将 OR 转换为 AND 的情况,只要您还使用他的Condition类实现来注释 Spring Bean。但我想提供另一种没有人提出的方法,它有其优点和缺点。

  • 方法 2:只需根据需要使用@Conditional和创建尽可能多的Condition实现。它的缺点是必须创建与组合一样多的实现,但如果你没有很多组合,在我看来,它是一个更简洁的解决方案,它提供了更大的灵活性和实现更复杂逻辑解决方案的机会。

方法 2的实施如下。

你的 Spring Bean:

@Component
@Conditional(value = { TestAndConfig1Profiles.class })
public class MyComponent {}

TestAndConfig1Profiles执行:

public class TestAndConfig1Profiles implements Condition {
    @Override
    public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().acceptsProfiles("TEST")
                    && context.getEnvironment().acceptsProfiles("CONFIG1");
    }
}

使用这种方法,您可以轻松涵盖更复杂的逻辑情况,例如:

(测试和配置1)| (测试和配置3)

只是想为您的问题提供更新的答案并补充其他答案。

于 2018-10-17T13:43:54.903 回答
26

由于 Spring 不提供开箱即用的 AND 功能。我建议以下策略:

目前@Profileannotation 有一个 conditional annotation @Conditional(ProfileCondition.class)ProfileCondition.class它遍历配置文件并检查配置文件是否处于活动状态。同样,您可以创建自己的条件实现并限制注册 bean。例如

public class MyProfileCondition implements Condition {

    @Override
    public boolean matches(final ConditionContext context,
            final AnnotatedTypeMetadata metadata) {
        if (context.getEnvironment() != null) {
            final MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
            if (attrs != null) {
                for (final Object value : attrs.get("value")) {
                    final String activeProfiles = context.getEnvironment().getProperty("spring.profiles.active");

                    for (final String profile : (String[]) value) {
                        if (!activeProfiles.contains(profile)) {
                            return false;
                        }
                    }
                }
                return true;
            }
        }
        return true;
    }

}

在你的课堂上:

@Component
@Profile("dev")
@Conditional(value = { MyProfileCondition.class })
public class DevDatasourceConfig

注意:我没有检查所有极端情况(如空值、长度检查等)。但是,这个方向可能会有所帮助。

于 2014-11-21T08:17:51.243 回答
8

@Mithun 回答的一点改进版本:

public class AndProfilesCondition implements Condition {

public static final String VALUE = "value";
public static final String DEFAULT_PROFILE = "default";

@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() == null) {
        return true;
    }
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs == null) {
        return true;
    }
    String[] activeProfiles = context.getEnvironment().getActiveProfiles();
    String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
    Set<String> allowedProfiles = new HashSet<>(1);
    Set<String> restrictedProfiles = new HashSet<>(1);
    for (String nextDefinedProfile : definedProfiles) {
        if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
            restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
            continue;
        }
        allowedProfiles.add(nextDefinedProfile);
    }
    int activeAllowedCount = 0;
    for (String nextActiveProfile : activeProfiles) {
        // quick exit when default profile is active and allowed profiles is empty
        if (DEFAULT_PROFILE.equals(nextActiveProfile) && allowedProfiles.isEmpty()) {
            continue;
        }
        // quick exit when one of active profiles is restricted
        if (restrictedProfiles.contains(nextActiveProfile)) {
            return false;
        }
        // just go ahead when there is no allowed profiles (just need to check that there is no active restricted profiles)
        if (allowedProfiles.isEmpty()) {
            continue;
        }
        if (allowedProfiles.contains(nextActiveProfile)) {
            activeAllowedCount++;
        }
    }
    return activeAllowedCount == allowedProfiles.size();
}

}

无法在评论中发布。

于 2015-09-18T09:12:59.907 回答
7

另一种选择是在@Profile注释允许的类/方法级别上播放。不如实施灵活,MyProfileCondition但如果适合您的情况,它会快速而干净。

例如,当 FAST 和 DEV 都处于活动状态时,这不会启动,但如果只有 DEV 是:

@Configuration
@Profile("!" + SPRING_PROFILE_FAST)
public class TomcatLogbackAccessConfiguration {

    @Bean
    @Profile({SPRING_PROFILE_DEVELOPMENT, SPRING_PROFILE_STAGING})
    public EmbeddedServletContainerCustomizer containerCustomizer() {
于 2016-03-30T11:23:19.353 回答
7

另一种可能适用于许多场景的技巧是将@Profile 注释放在@Configuration 上,将另一个@Profile 放在@Bean 上——这会在基于java 的spring 配置中的2 个配置文件之间创建逻辑与。

@Configuration
@Profile("Profile1")
public class TomcatLogbackAccessConfiguration {

   @Bean
   @Profile("Profile2")
   public EmbeddedServletContainerCustomizer containerCustomizer() {
于 2016-08-26T08:36:52.587 回答
6

我改进了@rozhoc 的答案,因为在使用@Profile 时,该答案没有考虑到没有配置文件等同于“默认”的事实。此外,我想要的条件是!default && !a@rozhoc 的代码没有正确处理。最后,我使用了一些 Java8,matches为了简洁起见,只展示了方法。

@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() == null) {
        return true;
    }
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs == null) {
        return true;
    }

    Set<String> activeProfilesSet = Arrays.stream(context.getEnvironment().getActiveProfiles()).collect(Collectors.toSet());
    String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
    Set<String> allowedProfiles = new HashSet<>(1);
    Set<String> restrictedProfiles = new HashSet<>(1);
    if (activeProfilesSet.size() == 0) {
        activeProfilesSet.add(DEFAULT_PROFILE);  // no profile is equivalent in @Profile terms to "default"
    }
    for (String nextDefinedProfile : definedProfiles) {
        if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
            restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
            continue;
        }
        allowedProfiles.add(nextDefinedProfile);
    }
    boolean allowed = true;
    for (String allowedProfile : allowedProfiles) {
        allowed = allowed && activeProfilesSet.contains(allowedProfile);
    }
    boolean restricted = true;
    for (String restrictedProfile : restrictedProfiles) {
        restricted = restricted && !activeProfilesSet.contains(restrictedProfile);
    }
    return allowed && restricted;
}

以下是您在令人困惑的情况下实际使用它的方式:

@Profile({"!default", "!a"})
@Conditional(value={AndProfilesCondition.class})
于 2016-03-30T21:11:37.763 回答
5

如果您已经使用 @Profile 注释标记了配置类或 bean 方法,则使用以下命令检查其他配置文件(例如 AND 条件)很简单Environment.acceptsProfiles()

@Autowired Environment env;

@Profile("profile1")
@Bean
public MyBean myBean() {
    if( env.acceptsProfiles("profile2") ) {
        return new MyBean();
    }
    else {
        return null;
    }
}
于 2015-12-18T09:52:39.610 回答