方法级别的Spring Profiles?

55

我想介绍一些仅在开发期间执行的方法。

我想在这里使用Spring @Profile注解?但是如何将此注解应用于类级别,以便只有在属性中配置了特定配置文件时才会调用此方法?

spring.profiles.active=dev

以下内容为伪代码,请问如何实现?

class MyService {

    void run() { 
       log();
    }

    @Profile("dev")
    void log() {
       //only during dev
    }
}
8个回答

43

对于那些不想使用多个用 @Profile 注释的 @Beans 的未来读者,这也可能是一种解决方法:

class MyService {

   @Autowired
   Environment env;

   void run() { 
      if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
         log();
      }
   }

   void log() {
      //only during dev
   }
}

16
环境中有笔误,应为Environment。另外,Arrays.asList(env.getActiveProfiles()).contains("dev")可以用env.acceptsProfiles("dev")来替代。 - Marko Vranjkovic
我更喜欢使用Arrays.asList().contains()的解决方案,在此上下文中acceptsProfiles不是很容易理解。 - funder7
@funder7,请解释一下创建一个新列表并在其上调用contains()比询问环境是否接受某个配置文件更易于理解或更高效的原因? - SergeyB
我的意思是 env.acceptsProfiles("dev") 更加隐晦,而另一种方式更加明确:env.getActiveProfiles 获取列表,.contains("dev") 检查感兴趣的配置文件是否包含在活动配置文件列表中。这只是一种风格上的偏好,没有其他意思。 :-) - funder7

22

如您在http://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/context/annotation/Profile.html上所读到的:

@Profile注释可以以以下任何方式之一使用:

作为类型级别注释直接或间接用于任何使用@ Component(包括@Configuration类)进行注释的类,作为元注释,用于组合自定义构造型注释。如果@Configuration类标记有@Profile,则该类相关的所有@Bean方法和@Import注释将被绕过,除非一个或多个指定的配置文件处于活动状态。 这非常类似于Spring XML中的行为:如果提供了豆子元素的配置文件属性,则不会解析豆子元素,除非已激活配置文件'p1'和/或'p2'。同样,如果@Component或@Configuration类标记有@Profile({"p1", "p2"}), 那个类只有当配置文件'p1' 或 'p2'被激活时才会被注册/处理。

因此,对类进行的@Profile注释适用于其所有方法和导入。而不是应用于类本身。

您可能想要做的事情可能可以通过具有实现相同接口的两个类来实现,根据配置文件注入其中一个或另一个来实现。请参见此问题的答案。

在不同环境下处理的注释驱动的依赖注入


好的,使用Spring注解可能无法实现我想要的功能。不过,你有没有任何想法如何实现这样的需求? - membersound
我正在写它。 - Andres
1
你可能希望同一个接口有多个实现,每个实现只在特定的配置文件激活时才会被包含在Spring上下文中。据我所知,你可能希望生产环境的实现对于该方法有一个空实现。 - geoand
如何使控制器处理程序方法仅在激活配置文件时可用?方法上的@Profile注释似乎被忽略了。 - Stephane

16

@Profile可以用于方法和Java Based Configuration

例如,为了将PostgreSQL(LocalDateTime)和HSQLDB(2.4.0之前的Timestamp)的时间戳进行区分:

@Autowired
private Function<LocalDateTime, T> dateTimeExtractor;

@Bean
@Profile("hsqldb")
public Function<LocalDateTime, Timestamp> getTimestamp() {
    return Timestamp::valueOf;
}

@Bean
@Profile("postgres")
public Function<LocalDateTime, LocalDateTime> getLocalDateTime() {
    return dt -> dt;
}

同时也可以查看Spring Profiles示例: 3. 更多…(演示Spring @Profile方法级别)


14

我想补充一下,这个回答声称当前Spring版本支持在方法级别上使用@Profile是明显错误的。通常情况下,在方法上使用@Profile将不起作用,唯一能够正常工作的是在@Configuration类中带有@Bean注释的方法。

我使用Spring 4.2.4进行了快速测试,结果如下:

  • @Configuration类中的bean创建方法中可以使用@Profile
  • 在bean的方法中使用@Profile不起作用(而且不应该有效,文档有些含糊不清)
  • 如果Class-level @Profile位于与bean定义相同的上下文中,或者在配置类中使用了context-scan,则它才会有效
  • env.acceptsProfiles("profile")和Arrays.asList(env.getActiveProfiles()).contains("profile")都是有效的

测试类:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Arrays;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ProfileTest.ProfileTestConfiguration.class })
@ActiveProfiles("test")
public class ProfileTest {
    static class SomeClass {}
    static class OtherClass {}
    static class ThirdClass {
        @Profile("test")
        public void method() {}
    }
    static class FourthClass {
        @Profile("!test")
        public void method() {}
    }

    static class ProfileTestConfiguration {
        @Bean
        @Profile("test")
        SomeClass someClass() {
            return new SomeClass();
        }
        @Bean
        @Profile("!test")
        OtherClass otherClass() {
            return new OtherClass();
        }
        @Bean
        ThirdClass thirdClass() {
            return new ThirdClass();
        }
        @Bean
        FourthClass fourthClass() {
            return new FourthClass();
        }
    }

    @Autowired
    ApplicationContext context;

    @Test
    public void testProfileAnnotationIncludeClass() {
        context.getBean(SomeClass.class);
    }
    @Test(expected = NoSuchBeanDefinitionException.class)
    public void testProfileAnnotationExcludeClass() {
        context.getBean(OtherClass.class);
    }
    @Test
    public void testProfileAnnotationIncludeMethod() {
        context.getBean(ThirdClass.class).method();
    }
    @Test(expected = Exception.class)  // fails
    public void testProfileAnnotationExcludeMethod() {
        context.getBean(FourthClass.class).method();
    }
}

8

1
截至4.0.x版本:http://docs.spring.io/spring/docs/4.0.x/javadoc-api/org/springframework/context/annotation/Profile.html - BoostHungry

7

@Profile 不能用于此,但是你可以编写自己的 @RunOnProfile 注解和方面。你的方法应该如下所示:

@RunOnProfile({"dev", "uat"})
public void doSomething() {
    log.info("I'm doing something on dev and uat");
}

或者

@RunOnProfile("!prod")
public void doSomething() {
    log.info("I'm doing something on profile other than prod");
}

以下是`@RunOnProfile`注解和切面的代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunOnProfile {
    String[] value();

    @Aspect
    @Configuration
    class RunOnProfileAspect {

        @Autowired
        Environment env;

        @Around("@annotation(runOnProfile)")
        public Object around(ProceedingJoinPoint joinPoint, RunOnProfile runOnProfile) throws Throwable {
            if (env.acceptsProfiles(runOnProfile.value()))
                return joinPoint.proceed();
            return null;
        }
    }
}

注意1: 如果您期望有所回报但不适用于该个人资料,则会得到null

注意2: 这类似于其他Spring切面。 您需要调用自动装配的bean方法,以便bean代理开始运行。 换句话说,您不能直接从其他类方法调用该方法。 如果您想要这样做,需要在类上进行自我注入,并调用self.doSomething()


3

使用acceptsProfiles(Profile)函数

class Test {

  @Autowired
  Environment env

  public void method1(){
     if(env.acceptsProfiles(Profiles.of("dev")){
        //invoke method
        doStuff();
     }
  }
}

嗨,Sourabh, 我们可以将多个配置文件写成逗号分隔的值吗?if(env.acceptsProfiles(Profiles.of("dev","test")){ //调用方法 doStuff(); }如果您认为这是正确的方式,请告诉我。 谢谢! - Shreya Maheshwari

1
使用环境配置文件可以实现此目的:
class MyClass {

   @Autowired
   Environment env;

   void methodA() { 
      if (env.acceptsProfiles(Profiles.of("dev"))) {
         methodB();
      }
   }

   void methodB() {
      // when profile is 'dev'
   }
}

Profiles.of也可以与逻辑表达式一起使用:

static Profiles of(String... profiles) 创建一个新的Profiles实例,检查是否与给定的配置文件字符串匹配。如果给定的任何一个配置文件字符串匹配,则返回的实例将匹配。

配置文件字符串可以包含简单的配置文件名称(例如“production”)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑,例如“production&cloud”。

配置文件表达式支持以下运算符:

!-配置文件的逻辑非&-配置文件的逻辑与| - 配置文件的逻辑或请注意,&和|运算符不能混合使用而不使用括号。例如,“a&b | c”不是有效的表达式;它必须表示为“(a&b) | c”或“a&(b | c)”。


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