Environment
인터페이스는 애플리케이션 환경의 두 가지 주요 측면인 profile과 properties을 모델링하는 컨테이너에 통합된 추상화입니다.
프로필은 주어진 프로필이 활성화된 경우에만 컨테이너에 등록되는 명명된(named) 논리적(logical) 빈 정의 그룹입니다. Bean은 XML 또는 어노테이션으로 정의된 프로필에 할당될 수 있습니다. 프로필과 관련된 Environment
객체의 역할은 현재 활성화된 프로필(있는 경우)과 기본적으로 활성화되어야 하는 프로필(있는 경우)을 결정하는 것입니다.
속성(Properties)은 거의 모든 애플리케이션에서 중요한 역할을 하며 properties 파일, JVM 시스템 속성(properties), 시스템 환경 변수, JNDI, 서블릿 컨텍스트 매개변수, 임시(ad-hoc) Properties
객체, Map
객체 등 다양한 소스에서 발생할 수 있습니다. 속성(properties)과 관련된 Environment
객체의 역할은 사용자에게 속성(property) 소스를 구성하고 속성(property)을 resolve하기 위한 편리한 서비스 인터페이스를 제공하는 것입니다.
Bean 정의 프로파일은 다양한 환경(environment)에서 다양한 Bean을 등록할 수 있는 메커니즘을 핵심(core) 컨테이너에 제공합니다. "환경"이라는 단어는 사용자마다 다른 의미를 가질 수 있으며 이 기능은 다음을 포함한 다양한 사용 사례에 도움이 될 수 있습니다.
개발 중에 인메모리 데이터 소스에 대해 작업하는 것 vs(versus) QA 또는 프로덕션 중에 JNDI에서 동일한 데이터 소스를 검색하는 것
성능 환경(performance envrionment)에 애플리케이션을 배포하는 경우에만 모니터링 인프라를 등록하기
customer A 대 customer B 배포에 대해 사용자 정의된 Bean 구현을 등록하기
DataSource
가 필요한 실제 애플리케이션의 첫 번째 사용 사례를 고려해보세요. 테스트 환경에서 구성은 다음과 유사할 수 있습니다.
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
이제 애플리케이션의 데이터 소스가 프로덕션 애플리케이션 서버의 JNDI 디렉토리에 등록되어 있다고 가정하고 이 애플리케이션을 QA 또는 프로덕션 환경에 배포할 수 있는 방법을 살펴보겠습니다. 이제 dataSource
빈은 다음 목록과 같습니다.
@Bean(destroyMethod = "")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
문제는 현재 환경(environment)에 따라 이 두 가지 변형을 어떻게 전환(switch)할지입니다. 시간이 지남에 따라 Spring 사용자는 이 작업을 수행하기 위한 여러 가지 방법을 고안했습니다. 일반적으로 환경 변수(environment variable) 값에 따라 올바른 구성 파일 경로(configuration file path)를 resolve하는 ${placeholder}
토큰이 포함된 XML <import/>
문과 시스템 환경 변수의 조합을 사용합니다. Bean 정의 프로파일은 이 문제에 대한 솔루션을 제공하는 핵심 컨테이너 기능입니다.
환경별 빈 정의의 이전 예제에 표시된 사용 사례를 일반화하면, 특정 컨텍스트에서는 특정 빈 정의를 등록해야 하지만 다른 컨텍스트에서는 등록하지 않아도 됩니다. 상황 A에서는 빈 정의의 특정 프로파일을 등록하고 상황 B에서는 다른 프로파일을 등록하고 싶다고 말할 수 있습니다. 우리는 이러한 요구를 반영(reflect)하기 위해 구성(configuration)을 업데이트하는 것부터 시작합니다.
@Profile
어노테이션을 사용하면 하나 이상의 지정된 프로필이 활성 상태일 때 컴포넌트를 등록할 수 있음을 나타낼 수 있습니다. 이전 예제를 사용하여 다음과 같이 dataSource
구성을 다시 작성할 수 있습니다.
@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();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod = "") // (1)
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
(1) @Bean(destroyMethod = "")
은 기본 destroy 메소드 추론을 비활성화합니다.
[Note]
앞서 언급했듯이@Bean
메소드를 사용하면 일반적으로 Spring의JndiTemplate
/JndiLocatorDelegate
헬퍼 또는JndiObjectFactoryBean
변형이 아닌, 이전에 표시된 JNDIInitialContext
사용법을 사용하여, 프로그래밍 방식의 JNDI 조회를 사용하도록 선택합니다. 그러면 반환 유형을 다음과 같이FactoryBean
타입으로 선언해야 합니다.
프로필 문자열에는 간단한 프로필 이름(예: production
) 또는 프로필 표현식(expression)이 포함될 수 있습니다. 프로필 표현식을 사용하면 보다 복잡한 프로필 논리를 표현할 수 있습니다(예: production & us-east
). 프로필 표현식에서는 다음 연산자가 지원됩니다.
!
: 프로필의 논리적 NOT
&
: 프로필의 논리 AND
|
: 프로필의 논리적 OR
[Note]
괄호를 사용하지 않고&
와|
를 혼합할 수 없습니다. 예를 들어,production & us-east | eu-central
은 유효한 표현이 아닙니다.production & (us-east | eu-central)
로 표현해야 합니다.
사용자 정의 구성(composed)된 어노테이션을 생성하기 위해 @Profile
을 메타 어노테이션으로 사용할 수 있습니다. 다음 예에서는 @Profile("production")
에 대한 드롭인 대체로 사용할 수 있는 사용자 정의 @Production
어노테이션을 정의합니다.
"Drop-in"은 보통 컴퓨터 과학 분야에서 사용되며, 기존의 구현체나 시스템을 수정하지 않고도 새로운 것으로 바꿀 수 있는 것을 가리킵니다. 즉, @Production
을 @Profile("production")
으로 대체하여 코드를 변경하지 않고도 동일한 기능을 수행할 수 있다는 것을 의미합니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
[Tip]
@Configuration
클래스가@Profile
로 표시되면 지정된 프로필 중 하나 이상이 활성화되지 않는 한 해당 클래스와 연결된 모든@Bean
메서드 및@Import
어노테이션이 무시(bypass)됩니다.@Component
또는@Configuration
클래스가@Profile({"p1", "p2"})
로 표시된 경우 해당 클래스는 'p1' 또는 'p2' 프로필이 활성화되지 않으면 등록되거나 처리되지 않습니다. 지정된 프로필에 NOT 연산자(!
)가 접두사(prefixed)로 붙은 경우 프로필이 활성화되지 않은 경우에만 어노테이션이 달린 요소가 등록됩니다. 예를 들어@Profile({"p1", "!p2"})
이 주어지면 'p1' 프로필이 활성 상태이거나 'p2' 프로필이 활성 상태가 아닌 경우 등록이 발생합니다.
@Profile
은 다음 예제와 같이 구성(configuration) 클래스의 특정 Bean 하나만 포함하도록 메서드 수준에서 선언할 수도 있습니다(예: 특정 Bean의 대체 변형).
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") // (1)
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") // (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
(1) standaloneDataSource
메서드는 development
프로필에서만 사용할 수 있습니다.
(2) jndiDataSource
메소드는 production
프로필에서만 사용할 수 있습니다.
[Note]
@Bean
메소드에@Profile
을 사용하면 특별한 시나리오가 적용될 수 있습니다. 동일한 Java 메소드 이름의@Bean
메소드가 오버로드된 경우(생성자 오버로딩과 유사)@Profile
조건은 오버로드된 모든 메소드에서 일관되게 선언되어야 합니다. 조건이 일치하지 않는 경우 오버로드된 메서드 중 첫 번째 선언의 조건만 중요합니다. 따라서@Profile
을 사용하여 특정 인수 시그니처가 있는 오버로드된 메서드를 다른 메서드 대신 선택할 수 없습니다. 동일한 빈에 대한 모든 팩토리 메소드 간의 resolution은 생성 시 Spring의 생성자 resolution 알고리즘을 따릅니다.다른 프로필 조건으로 대체 Bean을 정의하려면 이전 예제에 표시된 대로
@Bean
이름 속성(attribute)을 사용하여 동일한 Bean 이름을 가리키는 고유한(distinct) Java 메소드 이름을 사용하십시오. 인수 시그니처가 모두 동일한 경우(예를 들어 모든 변형에, 인수가 없는(no-arg) 팩토리 메서드가 있는 경우) 이는 처음에 유효한 Java 클래스에서 이러한 arrangement을 나타내는 유일한 방법입니다(특정 이름 및 인수 시그니처에는 하나의 메서드만 있을 수 있으므로).
XML 대응물은 <beans>
요소의 profile
속성(attribute)입니다. 이전 샘플 구성(configuration)은 다음과 같이 두 개의 XML 파일로 다시 작성할 수 있습니다.
<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>
다음 예제에서 볼 수 있듯이 동일한 파일 내에서 <beans/>
요소를 중첩하여, 분할되는 것을 방지하는 것도 가능합니다.
<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>
spring-bean.xsd
는 파일의 마지막 요소만 허용하도록 제한되었습니다. 이는 XML 파일을 복잡하게 만들지 않고 유연성을 제공하는 데 도움이 됩니다.
[Note]
XML 대응 부분은 앞에서 설명한 프로필 표현식을 지원하지 않습니다. 그러나!
연산자를 사용하여 프로필을 무효화하는 것이 가능합니다. 다음 예와 같이 프로필을 중첩하여 논리적 "and"를 적용하는 것도 가능합니다.<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>
앞의 예에서는
production
프로필과us-east
프로필이 모두 활성화된 경우dataSource
Bean이 노출됩니다.
이제 구성(configuration)을 업데이트했으므로 여전히 어떤 프로필이 활성화 되어 있는지 Spring에 지시해야 합니다. 지금 당장 샘플 애플리케이션을 시작했다면 컨테이너가 dataSource
라는 Spring 빈을 찾을 수 없기 때문에 NoSuchBeanDefinitionException
이 발생하는 것을 보게 될 것입니다.
프로필 활성화는 여러 가지 방법으로 수행할 수 있지만 가장 간단한 방법은 ApplicationContext
를 통해 사용할 수 있는 Environment
API에 대해 프로그래밍 방식으로 수행하는 것입니다. 다음 예에서는 그 방법을 보여줍니다.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
또한 시스템 환경 변수, JVM 시스템 속성(property), web.xml
의 서블릿 컨텍스트 매개변수 또는 JNDI의 항목(entry)을 통해(PropertySource
추상화 참조) 지정할 수 있는 spring.profiles.active
속성(property)을 통해 선언적으로 프로필을 활성화할 수도 있습니다. 통합 테스트에서는 spring-test
모듈의 @ActiveProfiles
어노테이션을 사용하여 활성 프로필을 선언할 수 있습니다(환경 프로필을 사용한 컨텍스트 구성 참조).
프로필은 "둘 중 하나(either-or)"라는 제안이 아닙니다. 한 번에 여러 프로필을 활성화할 수 있습니다. 프로그래밍 방식으로 String…
가변인수(varargs)를 허용하는 setActiveProfiles()
메서드에 여러 프로필 이름을 제공할 수 있습니다. 다음 예에서는 여러 프로필을 활성화합니다.
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
선언적으로 spring.profiles.active
는 다음 예제와 같이 쉼표로 구분된 프로필 이름 목록을 허용할 수 있습니다.
-Dspring.profiles.active="profile1,profile2"
기본 프로필은 활성화된 프로필이 없는 경우 활성화되는 프로필을 나타냅니다. 다음 예를 고려하십시오.
@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();
}
}
활성화된 프로필이 없으면 dataSource
가 생성됩니다. 이는 하나 이상의 Bean에 대한 기본 정의를 제공하는 방법으로 볼 수 있습니다. 프로필이 활성화되어 있으면 기본 프로필이 적용되지 않습니다.
기본 프로필의 이름은 default
입니다. Environment
에서 setDefaultProfiles()
를 사용하거나 선언적으로 spring.profiles.default
속성(property)을 사용하여 기본 프로필의 이름을 변경할 수 있습니다.
PropertySource
AbstractionSpring의 Environment
추상화는 구성 가능한 속성(property) 소스 계층에 대한 검색 작업을 제공합니다. 다음 목록을 고려하십시오.
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);
이전 코드 조각에서 우리는 my-property
속성(property)이 현재 환경에 대해 정의되어 있는지 Spring에 묻는 높은 수준의 방법을 볼 수 있습니다. 이 질문에 대답하기 위해 Environment
객체는 일련의 PropertySource
객체에 대해 검색을 수행합니다. PropertySource
는 키-값 쌍의 모든 소스에 대한 간단한 추상화이며 Spring의 StandardEnvironment
는 두 개의 PropertySource 객체로 구성됩니다. 하나는 JVM 시스템 속성(property) 집합(System.getProperties()
)을 나타내고 다른 하나는 시스템 환경 변수 집합( System.getenv()
)을 나타냅니다.
[Note]
이러한 기본 속성 소스는 독립 실행형 애플리케이션에서 사용하기 위해StandardEnvironment
에 제공됩니다.StandardServletEnvironment
는 서블릿 구성, 서블릿 컨텍스트 매개변수 및 JNDI를 사용할 수 있는 경우JndiPropertySource
를 포함한 추가 기본 속성(property) 소스로 채워집니다.
구체적으로(Concretely) StandardEnvironment
를 사용할 때 런타임에 my-property
시스템 속성(property)이나 my-property
환경 변수가 있는 경우 env.containsProperty("my-property")
에 대한 호출은 true를 반환합니다.
[Tip]
수행되는 검색은 계층적입니다. 기본적으로, 시스템 속성(property)은 환경 변수보다 우선합니다. 따라서env.getProperty("my-property")
를 호출하는 동안my-property
속성(property)이 두 위치 모두에 설정되면, 시스템 속성 값이 "승리"하여 반환됩니다. 속성 값은 병합되지 않고 오히려 이전 항목에 의해 완전히 재정의됩니다.공통
StandardServletEnvironment
의 경우 전체 계층 구조는 다음과 같으며 우선 순위가 가장 높은 항목이 맨 위에 표시됩니다.
ServletConfig
매개변수(해당되는 경우 — 예를 들어DispatcherServlet
컨텍스트의 경우)
ServletContext
매개변수(web.xml
context-param
항목)JNDI 환경 변수(
java:comp/env/
항목)JVM 시스템 특성(
-D
명령줄 인수)JVM 시스템 환경(운영 체제 환경 변수)
가장 중요한 점은 전체 메커니즘을 구성(configurable)할 수 있다는 것입니다. 아마도 이 검색에 통합하려는 사용자 정의 속성 소스가 있을 수 있습니다. 그렇게 하려면 자신만의 PropertySource
를 구현(implement) 및 인스턴스화하고 이를 현재 Environment
의 PropertySource
세트에 추가하세요. 다음 예에서는 그 방법을 보여줍니다.
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
앞의 코드에서는 MyPropertySource
가 검색에서 가장 높은 우선 순위로 추가되었습니다. my-property
속성(property)이 포함된 경우, 다른 PropertySource
의 my-property
속성(property)을 위해 해당 속성(property)이 감지되고 반환됩니다. MutablePropertySources
API는 속성 소스 세트를 정확하게 조작할 수 있는 다양한 메서드를 노출합니다.
@PropertySource
@PropertySource 어노테이션은 Spring Environment
에 PropertySource
를 추가하기 위한 편리하고 선언적인 메커니즘을 제공합니다.
키-값 쌍 testbean.name=myTestBean
을 포함하는 app.properties
라는 파일이 있는 경우 다음 @Configuration
클래스는 testBean.getName()
호출이 myTestBean
을 반환하는 방식으로 @PropertySource
를 사용합니다.
@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;
}
}
@PropertySource
리소스 위치에 있는 모든 ${…}
자리 표시자는 다음 예제와 같이 환경에 대해 이미 등록된 속성 소스 집합에 대해 resolve됩니다.
@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;
}
}
my.placeholder
가 이미 등록된 속성 소스(예: 시스템 속성 또는 환경 변수) 중 하나에 있다고 가정하면 자리 표시자는 해당 값으로 resolve됩니다. 그렇지 않은 경우 default/path
가 기본값으로 사용됩니다. 기본값이 지정되지 않고 속성(property)을 resolve할 수 없는 경우 IllegalArgumentException
이 발생합니다.
[Note]
@PropertySource
는 반복 가능한 어노테이션으로 사용될 수 있습니다.@PropertySource
는 속성 재정의로 사용자 정의 구성(composed)된 어노테이션을 생성하기 위한 메타 어노테이션으로 사용될 수도 있습니다.
역사적으로 요소의 자리 표시자 값은 JVM 시스템 속성이나 환경 변수에 대해서만 resolve될 수 있었습니다. 더 이상 그렇지 않습니다. Environment
추상화는 컨테이너 전체에 통합되어 있으므로, 이를 통해 자리 표시자의 resolution 경로를 쉽게 지정할 수 있습니다. 즉, 원하는 방식으로 resolution 프로세스를 구성(configure)할 수 있습니다. 시스템 속성(property) 및 환경 변수를 통해 검색 우선 순위를 변경하거나 완전히 제거할 수 있습니다. 필요에 따라 자신만의 속성(property) 소스를 믹스에 추가할 수도 있습니다.
구체적으로(Concretely) 다음 명령문은 customer
속성(property)이 Environment
에서 사용 가능한 한, 정의된 위치에 관계없이 작동합니다.
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>