Environment Abstraction
Spring IoC Container (24편)
- Spring Framework Overview
- Core Technologies
- Container Overview
- Bean Overview
- Dependencies and Configuration in Detail
- Using depends-on
- Lazy-initialized Beans
- Method Injection
- Bean Scopes
- Customizing the Nature of a Bean
- Bean Definition Inheritance
- Container Extension Points
- Annotation-based Container Configuration
- Classpath Scanning and Managed Components
- Using JSR-330 Standard Annotations
- Basic Concepts: @Bean and @Configuration
- Using the @Bean Annotation
- Using the @Configuration annotation
- Instantiating the Spring Container by Using AnnotationConfigApplicationContext
- Autowiring Collaborators
- Environment Abstraction
- Registering a LoadTimeWeaver
- Additional Capabilities of the ApplicationContext
- The BeanFactory API
목차
전문 번역
개요 (Overview)
Environment 인터페이스는 컨테이너에 통합된 추상화로, 애플리케이션 환경의 두 가지 핵심 측면을 모델링합니다: 프로파일(profiles)과 프로퍼티(properties).
- 프로파일(profile)은 주어진 프로파일이 활성화된 경우에만 컨테이너에 등록되는 빈 정의들의 명명된 논리적 그룹입니다.
- 프로퍼티(properties)는 거의 모든 애플리케이션에서 중요한 역할을 하며, 프로퍼티 파일, JVM 시스템 프로퍼티, 시스템 환경 변수, JNDI, 서블릿 컨텍스트 파라미터 등 다양한 소스에서 유래할 수 있습니다.
빈 정의 프로파일 (Bean Definition Profiles)
빈 정의 프로파일은 코어 컨테이너에서 서로 다른 환경에서 서로 다른 빈을 등록할 수 있게 하는 메커니즘을 제공합니다.
일반적인 사용 사례 (Common Use Cases)
- 개발 환경에서는 인메모리 데이터소스를 사용하지만 QA 또는 운영 환경에서는 JNDI에서 동일한 데이터소스를 조회하는 경우
- 성능 환경으로 애플리케이션을 배포할 때만 모니터링 인프라를 등록하는 경우
- 서로 다른 고객 배포를 위해 빈의 커스터마이징된 구현을 등록하는 경우
@Profile 사용하기 (Using @Profile)
@Profile 애노테이션은 하나 이상의 지정된 프로파일이 활성화될 때 컴포넌트가 등록 대상이 됨을 나타낼 수 있게 합니다.
개발 환경 설정 예시 (Development Configuration Example)
Java:
@Configuration@Profile("development")public class StandaloneDataConfig {
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); }}Kotlin:
@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() }}운영 환경 설정 예시 (Production Configuration Example)
Java:
@Configuration@Profile("production")public class JndiDataConfig {
@Bean(destroyMethod = "") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}Kotlin:
@Configuration@Profile("production")class JndiDataConfig {
@Bean(destroyMethod = "") fun dataSource(): DataSource { val ctx = InitialContext() return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource }}프로파일 표현식 (Profile Expressions)
프로파일 문자열은 단순한 프로파일 이름(예: production) 또는 프로파일 표현식을 포함할 수 있습니다.
지원되는 연산자 (Supported operators):
!: 논리 NOT&: 논리 AND|: 논리 OR
예시: production & (us-east | eu-central)
⚠️ 괄호를 사용하지 않고
&와|연산자를 혼합할 수 없습니다.
메서드 레벨 @Profile (Method-Level @Profile)
@Profile은 메서드 레벨에서도 선언되어 특정 빈 변형만 포함할 수 있습니다:
Java:
@Configurationpublic class AppConfig {
@Bean("dataSource") @Profile("development") public DataSource standaloneDataSource() { return new 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") public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}Kotlin:
@Configurationclass AppConfig {
@Bean("dataSource") @Profile("development") 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") fun jndiDataSource() = InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource}XML 빈 정의 프로파일 (XML Bean Definition Profiles)
XML에서 대응되는 것은 <beans> 엘리먼트의 profile 속성입니다:
<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>
<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>단일 파일에 중첩된 프로파일 (Nested Profiles in Single File)
<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>중첩된 프로파일을 사용한 논리 AND (Logical AND with Nested Profiles)
<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>dataSource 빈은 production과 us-east 프로파일이 모두 활성화된 경우에만 노출됩니다.
프로파일 활성화 (Activating a Profile)
프로파일은 Environment API를 사용하여 프로그래밍 방식으로 활성화할 수 있습니다:
Java:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();ctx.getEnvironment().setActiveProfiles("development");ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);ctx.refresh();Kotlin:
val ctx = AnnotationConfigApplicationContext().apply { environment.setActiveProfiles("development") register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java) refresh()}선언적 활성화 (Declarative Activation)
프로파일은 spring.profiles.active 프로퍼티를 통해서도 활성화될 수 있습니다:
- 시스템 환경 변수
- JVM 시스템 프로퍼티
web.xml의 서블릿 컨텍스트 파라미터- JNDI 엔트리
- 통합 테스트에서
@ActiveProfiles애노테이션 사용
여러 프로파일 활성화 (Activating Multiple Profiles)
프로그래밍 방식:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");선언적 방식:
-Dspring.profiles.active="profile1,profile2"기본 프로파일 (Default Profile)
기본 프로파일은 활성화된 프로파일이 없을 때 활성화됩니다:
Java:
@Configuration@Profile("default")public class DefaultDataConfig {
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); }}Kotlin:
@Configuration@Profile("default")class DefaultDataConfig {
@Bean fun dataSource(): DataSource { return EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build() }}기본 프로파일 이름은 default이며 다음을 사용하여 변경할 수 있습니다:
Environment의setDefaultProfiles()spring.profiles.default프로퍼티
PropertySource 추상화 (PropertySource Abstraction)
Spring의 Environment 추상화는 설정 가능한 프로퍼티 소스 계층 구조에 대한 검색 작업을 제공합니다.
기본 사용법 (Basic Usage)
Java:
ApplicationContext ctx = new GenericApplicationContext();Environment env = ctx.getEnvironment();boolean containsMyProperty = env.containsProperty("my-property");System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);Kotlin:
val ctx = GenericApplicationContext()val env = ctx.environmentval containsMyProperty = env.containsProperty("my-property")println("Does my environment contain the 'my-property' property? $containsMyProperty")기본 프로퍼티 소스 (Default Property Sources)
StandardEnvironment는 두 개의 PropertySource 객체로 구성됩니다:
- JVM 시스템 프로퍼티 (
System.getProperties()) - 시스템 환경 변수 (
System.getenv())
StandardServletEnvironment는 추가적인 기본 프로퍼티 소스를 포함합니다:
- ServletConfig 파라미터
- ServletContext 파라미터
- JNDI 환경 변수
- JndiPropertySource (JNDI를 사용할 수 있는 경우)
프로퍼티 소스 계층 구조 (Property Source Hierarchy - StandardServletEnvironment)
프로퍼티는 다음 순서로 검색됩니다 (우선순위가 높은 것부터):
- ServletConfig 파라미터 (예: DispatcherServlet 컨텍스트)
- ServletContext 파라미터 (web.xml의 context-param 엔트리)
- JNDI 환경 변수 (java:comp/env/ 엔트리)
- JVM 시스템 프로퍼티 (-D 커맨드라인 인수)
- JVM 시스템 환경 (운영 체제 환경 변수)
참고: 프로퍼티 값은 병합되지 않고 선행 엔트리에 의해 완전히 오버라이드됩니다.
커스텀 프로퍼티 소스 (Custom Property Sources)
커스텀 프로퍼티 소스를 추가하려면:
Java:
ConfigurableApplicationContext ctx = new GenericApplicationContext();MutablePropertySources sources = ctx.getEnvironment().getPropertySources();sources.addFirst(new MyPropertySource());Kotlin:
val ctx = GenericApplicationContext()val sources = ctx.environment.propertySourcessources.addFirst(MyPropertySource())MutablePropertySources API는 프로퍼티 소스의 정밀한 조작을 위한 메서드를 제공합니다.
@PropertySource 사용하기 (Using @PropertySource)
@PropertySource 애노테이션은 Spring의 Environment에 PropertySource를 추가하기 위한 편리하고 선언적인 메커니즘을 제공합니다.
기본 예시 (Basic Example)
다음 내용을 가진 app.properties 파일이 주어졌을 때:
testbean.name=myTestBeanJava:
@Configuration@PropertySource("classpath:/com/myco/app.properties")public class AppConfig {
@Autowired Environment env;
@Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; }}Kotlin:
@Configuration@PropertySource("classpath:/com/myco/app.properties")class AppConfig {
@Autowired private lateinit var env: Environment
@Bean fun testBean() = TestBean().apply { name = env.getProperty("testbean.name")!! }}플레이스홀더 해석을 사용한 예시 (With Placeholder Resolution)
@PropertySource 리소스 위치의 플레이스홀더는 이미 등록된 프로퍼티 소스 세트에 대해 해석됩니다:
Java:
@Configuration@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")public class AppConfig {
@Autowired Environment env;
@Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; }}Kotlin:
@Configuration@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")class AppConfig {
@Autowired private lateinit var env: Environment
@Bean fun testBean() = TestBean().apply { name = env.getProperty("testbean.name")!! }}my.placeholder가 등록된 프로퍼티 소스에 존재하면 해석됩니다. 그렇지 않으면 default/path가 기본값으로 사용됩니다.
⚠️ 기본값이 지정되지 않고 프로퍼티를 해석할 수 없는 경우
IllegalArgumentException이 발생합니다.
기능 (Features)
@PropertySource는 반복 가능한 애노테이션으로 사용할 수 있습니다@PropertySource는 커스텀 합성 애노테이션을 생성하기 위한 메타 애노테이션으로 사용할 수 있습니다
구문에서의 플레이스홀더 해석 (Placeholder Resolution in Statements)
역사적으로 플레이스홀더는 JVM 시스템 프로퍼티나 환경 변수에 대해서만 해석될 수 있었습니다. 이는 더 이상 사실이 아닙니다. Environment 추상화는 컨테이너 전체에 통합되어 유연한 플레이스홀더 해석을 가능하게 합니다.
예시 (Example)
다음 구문은 customer 프로퍼티가 Environment에서 사용 가능하기만 하면 어디에 정의되어 있든 작동합니다:
<beans> <import resource="com/bank/service/${customer}-config.xml"/></beans>탐색 (Navigation)
이전: 프로그래밍 방식의 빈 등록 (Programmatic Bean Registration)
다음: LoadTimeWeaver 등록하기 (Registering a LoadTimeWeaver)