Home [Spring] 1.13.1. Bean Definition Profiles
Post
Cancel

[Spring] 1.13.1. Bean Definition Profiles

1.13.1. Bean Definition Profiles

Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments. The word, “environment,” can mean different things to different users, and this feature can help with many use cases, including:

  • Bean definition profiles은 core container에 서로 다른 환경에서 서로 다른 beans을 등록할 수 있는 메커니즘을 제공한다. “environment”이라는 단어는 사용자마다 다른 의미를 가질 수 있으며, 이 기능은 다음을 포함한 많은 사용 사례에 도움이 될 수 있다.
  • use cases

    Working against an in-memory datasource in development versus looking up that same datasource from JNDI when in QA or production.

    • QA 또는 프로덕션에서 JNDI로부터 동일한 데이터 소스를 조회하는 대신 개발 중인 in-memory datasource와 비교.

    Registering monitoring infrastructure only when deploying an application into a performance environment.

    • performance environment에 애플리케이션을 배포하는 경우에만 모니터링 인프라 등록

    Registering customized implementations of beans for customer A versus customer B deployments.

    • 고객 A와 고객 B를 위한 customized implementations of beans 등록.

Consider the first use case in a practical application that requires a DataSource. In a test environment, the configuration might resemble the following:

  • DataSource가 필요한 실제 애플리케이션의 첫 번째 사용 사례를 고려해보자.
  • 테스트 환경에서 configuration은 다음과 유사할 수 있다.
1
2
3
4
5
6
7
8
@Bean
fun dataSource(): DataSource {
    return EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("my-schema.sql")
            .addScript("my-test-data.sql")
            .build()
}

Now consider how this application can be deployed into a QA or production environment, assuming that the datasource for the application is registered with the production application server’s JNDI directory. Our dataSource bean now looks like the following listing:

  • 이제 애플리케이션의 datasource가 프로덕션 애플리케이션 서버의 JNDI 디렉터리에 등록되어 있다고 가정할 때 이 애플리케이션을 QA 또는 프로덕션 환경에 배포하는 방법을 생각해 보자.
  • 현재 dataSource bean은 다음과 같은 목록으로 표시된다:
1
2
3
4
5
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    val ctx = InitialContext()
    return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}

The problem is how to switch between using these two variations based on the current environment. Over time, Spring users have devised a number of ways to get this done, usually relying on a combination of system environment variables and XML <import/> statements containing ${placeholder} tokens that resolve to the correct configuration file path depending on the value of an environment variable. Bean definition profiles is a core container feature that provides a solution to this problem.

  • 문제는 현재 environment에 따라 이 두 가지 변형(variations)을 어떻게 사용하느냐다.
  • 시간이 지나면서 Spring 사용자들은 보통 환경변수의 값에 따라 올바른 구성파일 경로로 해결하는 ${placeholder} 토큰이 포함된 시스템 환경변수와 XML <import/> 문들의 조합에 의존하여 이 작업을 수행하는 여러 가지 방법을 고안해냈다.
  • Bean definition profiles은 이 문제에 대한 해결책을 제공하는 core container 기능이다.

If we generalize the use case shown in the preceding example of environment-specific bean definitions, we end up with the need to register certain bean definitions in certain contexts but not in others. You could say that you want to register a certain profile of bean definitions in situation A and a different profile in situation B. We start by updating our configuration to reflect this need.

  • environment-specific bean definitions의 앞의 예에서 나타낸 사용 사례를 일반화하면, 특정 bean definitions를 특정 contexts에서는 등록하지 않고 다른 contexts에서는 등록하지 않아도 되는 상황에 이르게 된다.
  • A 상황에서는 bean definitions의 특정 profile을 등록하고 B 상황에서는 다른 profile을 등록하고 싶다고 말할 수 있다.
  • 이러한 요구를 반영하려면 먼저 구성을 업데이트하자.

Using @Profile

The @Profile annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the dataSource configuration as follows:

  • @Profile annotation을 사용하면 하나 이상의 지정된 profiles이 활성 상태일 때 component가 등록될 수 있음을 나타낼 수 있다.
  • 앞의 예를 사용하여 다음과 같이 dataSource 구성을 다시 작성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@Profile("development")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
1
2
3
4
5
6
7
8
9
10
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}

As mentioned earlier, with @Bean methods, you typically choose to use programmatic JNDI lookups, by using either Spring’s JndiTemplate/ JndiLocatorDelegate helpers or the straight JNDI InitialContext usage shown earlier but not the JndiObjectFactoryBean variant, which would force you to declare the return type as the FactoryBean type.

  • 앞에서 언급한 바와 같이, @Bean methods에서는, 일반적으로 스프링의 JndiTemplate/ JndiLocatorDelegate helpers 또는 앞에서 보여진 JNDI InitialContext 사용법을 사용하여 프로그래밍 방식의(programmatic) JNDI lookups을 선택하며, FactoryBean type같은 return type을 강제적으로 선언하도록 만드는 JndiObjectFactoryBean varient를 선택하지 않는다.

The profile string may contain a simple profile name (for example, production) or a profile expression. A profile expression allows for more complicated profile logic to be expressed (for example, production & us-east). The following operators are supported in profile expressions:

  • profile 문자열은 간단한 프로필 이름(예: production) 또는 profile expression을 포함할 수 있다.
  • profile expression을 사용하면 보다 복잡한 profile logic를 표현할 수 있다(예: production & us-east).
  • profile expressions에서 지원되는 연산자는 다음과 같다.

You cannot mix the & and | operators without using parentheses. For example, production & us-east | eu-central is not a valid expression. It must be expressed as production & (us-east | eu-central).

  • 괄호를 사용하지 않고 &| 연산자를 혼합할 수 없다.
  • 예를 들어, production & us-east | eu-central은 유효한 표현이 아니다.
  • 그것은 production & (us-east | eu-central)로 표현되어야 한다.

You can use @Profile as a meta-annotation for the purpose of creating a custom composed annotation. The following example defines a custom @Production annotation that you can use as a drop-in replacement for @Profile("production"):

  • custom composed annotation을 만들 목적으로 @Profilemeta-annotation으로 사용할 수 있다.
  • 다음 예에서는 @Profile("production")의 drop-in replacement로 사용할 수 있는 custom @Production annotation을 정의한다.
1
2
3
4
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production

If a @Configuration class is marked with @Profile, all of the @Bean methods and @Import annotations associated with that class are bypassed unless one or more of the specified profiles are active. If a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class is not registered or processed unless profiles ‘p1’ or ‘p2’ have been activated. If a given profile is prefixed with the NOT operator (!), the annotated element is registered only if the profile is not active. For example, given @Profile({"p1", "!p2"}), registration will occur if profile ‘p1’ is active or if profile ‘p2’ is not active.

  • @Configuration 클래스에 @Profile이 표시되면 지정한 프로파일 중 하나 이상이 활성화되지 않는 한 해당 클래스와 연관된 모든 @Bean 메서드와 @Import annotations은 우회된다.
  • @Component 또는 @Configuration 클래스가 @Profile({"p1", "p2"})으로 표시된 경우, 프로파일 ‘p1’ 또는 ‘p2’가 활성화되지 않은 한 해당 클래스는 등록되거나 처리되지 않는다.
  • 주어진 프로파일에 NOT 연산자(!)가 접두사인 경우, 주석이 달린 요소는 프로파일이 활성화되지 않은 경우에만 등록된다.
  • 예를 들어 @Profile({"p1", "!p2"})을 지정하면 프로파일 ‘p1’이 활성화되어 있거나 프로파일 ‘p2’이 활성화되어 있지 않으면 등록이 이루어진다.

@Profile can also be declared at the method level to include only one particular bean of a configuration class (for example, for alternative variants of a particular bean), as the following example shows:

  • @Profile은 다음 예에서 알 수 있듯이 configuration 클래스의 특정 bean(예: 특정 bean의 대체 변형(alternative variants))을 하나만 포함하도록 방법 수준에서 선언할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
class AppConfig {

    @Bean("dataSource")
    @Profile("development") // 1
    fun standaloneDataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }

    @Bean("dataSource")
    @Profile("production") // 2
    fun jndiDataSource() =
        InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
  1. The standaloneDataSource method is available only in the development profile.

  2. The jndiDataSource method is available only in the production profile.

  • standaloneDataSource method은 development profile에서만 사용할 수 있다.
  • jndiDataSource method은 production profile에서만 사용할 수 있다.

With @Profile on @Bean methods, a special scenario may apply: In the case of overloaded @Bean methods of the same Java method name (analogous to constructor overloading), a @Profile condition needs to be consistently declared on all overloaded methods. If the conditions are inconsistent, only the condition on the first declaration among the overloaded methods matters. Therefore, @Profile can not be used to select an overloaded method with a particular argument signature over another. Resolution between all factory methods for the same bean follows Spring’s constructor resolution algorithm at creation time.

If you want to define alternative beans with different profile conditions, use distinct Java method names that point to the same bean name by using the @Bean name attribute, as shown in the preceding example. If the argument signatures are all the same (for example, all of the variants have no-arg factory methods), this is the only way to represent such an arrangement in a valid Java class in the first place (since there can only be one method of a particular name and argument signature).

  • @Bean 방법에 대한 @Profile을 사용하면 다음과 같은 특수한 시나리오를 적용할 수 있다.
  • (constructor overloading과 유사한) 동일한 Java 메서드 이름의 overloaded @Bean methods의 경우, 모든 overloaded methods에 대해 @Profile condition을 일관성 있게 선언할 필요가 있다.
  • 조건이 일치하지 않으면 overloaded methods 중 1차 declaration 조건만 문제가 된다.
  • 따라서 @Profile은 다른 것에 대한 특정 argument signature이 있는 overloaded method을 선택하는 데 사용할 수 없다.
  • 동일한 bean에 대한 모든 factory methods 사이의 Resolution은 생성시점에 스프링의 constructor resolution algorithm을 따른다.

  • 프로필 조건이 다른 alternative beans을 정의하려면 앞의 예와 같이 @Bean name 속성을 사용하여 동일한 bean 이름을 가리키는 다른 Java method names을 사용하자.
  • 만일 argument signatures이 모두 동일하다면(예를 들어, 모든 variants에는 no-arg factory methods이 있다), 이것은 애당초 유효한 자바 클래스에서 그러한 arrangement를 나타낼 수 있는 유일한 방법이다(특정 이름과 argument signature의 방법은 하나만 있을 수 있기 때문이다).

XML Bean Definition Profiles

The XML counterpart is the profile attribute of the <beans> element. Our preceding sample configuration can be rewritten in two XML files, as follows:

  • XML counterpart는 <beans> 요소의 profile 속성이다.
  • 이전 샘플 configuration은 다음과 같이 두 개의 XML 파일로 다시 작성될 수 있다.
1
2
3
4
5
6
7
8
9
10
11
<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
1
2
3
4
5
6
7
8
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

It is also possible to avoid that split and nest <beans/> elements within the same file, as the following example shows:

  • 다음 예에서 알 수 있듯이, 같은 파일 내에 <beans/> 원소를 분할하여 내포하는 것도 피할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

The spring-bean.xsd has been constrained to allow such elements only as the last ones in the file. This should help provide flexibility without incurring clutter in the XML files.

  • spring-bean.xsd는 파일의 마지막 요소와 같은 요소만 허용하도록 제한되었다.
  • 이것은 XML 파일에 잡음을 일으키지 않고 유연성을 제공하는 데 도움이 될 것이다.

The XML counterpart does not support the profile expressions described earlier. It is possible, however, to negate a profile by using the ! operator. It is also possible to apply a logical “and” by nesting the profiles, as the following example shows:

  • XML counterpart은 앞에서 설명한 profile expressions을 지원하지 않는다.
  • 그러나 ! 연산자를 사용하여 프로필을 부정하는 것은 가능하다.
  • 또한 다음과 같은 예에서 알 수 있듯이 프로파일을 중첩하여 논리적 “and”을 적용할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>

In the preceding example, the dataSource bean is exposed if both the production and us-east profiles are active.

  • 앞의 예에서 dataSource bean은 production 프로파일과 us-east 프로파일이 모두 활성 상태인 경우 노출된다.

Activating a Profile

Now that we have updated our configuration, we still need to instruct Spring which profile is active. If we started our sample application right now, we would see a NoSuchBeanDefinitionException thrown, because the container could not find the Spring bean named dataSource.

  • 이제 configuration을 업데이트했으므로 Spring에서 어떤 프로필이 활성 상태인지 알려야 한다.
  • 지금 바로 샘플 애플리케이션을 시작하면 NoSuchBeanDefinitionException이 나타날 겁니다.
  • 왜냐하면 컨테이너가 dataSource라는 이름의 Spring bean을 찾을 수 없기 때문에 예외 발생하기 때문이다.

Activating a profile can be done in several ways, but the most straightforward is to do it programmatically against the Environment API which is available through an ApplicationContext. The following example shows how to do so:

  • 프로파일을 활성화하는 것은 여러 가지 방법으로 이루어질 수 있지만, 가장 간단한 방법은 ApplicationContext를 통해 이용할 수 있는 Environment API에 대해 프로그래밍하는 것이다.
  • 다음 예제는 이를 수행하는 방법을 보여준다.
1
2
3
4
5
val ctx = AnnotationConfigApplicationContext().apply {
    environment.setActiveProfiles("development")
    register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
    refresh()
}

In addition, you can also declaratively activate profiles through the spring.profiles.active property, which may be specified through system environment variables, JVM system properties, servlet context parameters in web.xml, or even as an entry in JNDI (see [PropertySource Abstraction](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-property-source-abstraction)). In integration tests, active profiles can be declared by using the @ActiveProfiles annotation in the spring-test module (see context configuration with environment profiles).

  • 또한 시스템 환경 변수, JVM 시스템 속성, web.xml의 서블릿 컨텍스트 매개 변수 또는 JNDI의 항목으로 지정할 수 있는 spring.profiles.active 속성을 통해 선언적으로 프로파일을 활성화할 수도 있다(see [PropertySource Abstraction](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-property-source-abstraction)).
  • 통합 테스트에서 활성 프로파일은 spring-test module의 @ActiveProfiles annotation을 사용하여 선언할 수 있다(see context configuration with environment profiles).

Note that profiles are not an “either-or” proposition. You can activate multiple profiles at once. Programmatically, you can provide multiple profile names to the setActiveProfiles() method, which accepts String… varargs. The following example activates multiple profiles:

  • 프로파일은 “둘 중 하나” 제안이 아니라는 점에 유의하자.
  • 우리는 한 번에 여러 프로필을 활성화할 수 있다.
  • 프로그래밍 방식으로, 당신은 String… varargs를 수용하는 setActiveProfiles() method에 다중 profile names을 제공할 수 있다.
  • 다음은 여러 프로필을 활성화하는 예:
1
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")

Declaratively, spring.profiles.active may accept a comma-separated list of profile names, as the following example shows:

  • 선언적으로 spring.profiles.active는 다음과 같은 예에서 알 수 있듯이 쉼표로 구분된 프로파일 이름 목록을 허용할 수 있다.
1
-Dspring.profiles.active="profile1,profile2"

Default Profile

The default profile represents the profile that is enabled by default. Consider the following example:

  • 기본 프로필은 기본적으로 사용하도록 설정된 프로필을 나타낸다.
  • 다음 예를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}

If no profile is active, the dataSource is created. You can see this as a way to provide a default definition for one or more beans. If any profile is enabled, the default profile does not apply.

  • 활성 프로필이 없으면 dataSource가 생성된다.
  • 이를 하나 이상의 beans에 대한 기본 정의를 제공하는 방법으로 볼 수 있다.
  • 프로필이 활성화된 경우 기본 프로필이 적용되지 않는다.

You can change the name of the default profile by using setDefaultProfiles() on the Environment or ,declaratively, by using the spring.profiles.default property.

  • EnvironmentsetDefaultProfiles()를 사용하거나, 또는 선언적으로 spring.profiles.default 속성을 사용하여 기본 프로파일의 이름을 변경할 수 있다.

References

This post is licensed under CC BY 4.0 by the author.

[Spring] 1.13. Environment Abstraction

[Spring] 1.13.2. PropertySource Abstraction