1.5.5 프로퍼티 소스
- 애플리케이션의 기능과 구현 방법이 변경되지 않으면 구성정보를 변경할 필요는 없음
- DB 연결정보처럼 환경에 따라 달라지는 것은 빈 메타정보에 두기보다 프로퍼티 파일 같은 별도의 리소스를 사용해 분리하는 편이 바람직한 방법
- 런타임 환경에 따라 빈의 구성이나 구현 클래스가 달라지는 경우라면 프로파일을 사용
- 프로파일을 사용하는 경우에도 외부 리소스에 따라 바뀔 수 있는 DB 연결정보는 메타정보 외부로 독립시킬 필요가 있음
- 필요에 따라
<context:property-placeholder>
와 ${} 치환자를 사용해 파일로부터 프로퍼티 정보를 가져와서 사용
프로퍼티
- 기본적으로 키와 그에 대응되는 값의 쌍을 의미
- 스프링의 XML에서는 <property>의 name과 value 애트리뷰트를 이용해 프로퍼티 정보 표현
- DB관련 정보를 XML이나 자바 코드 외부에 둘때 Properties가 지원하는 프로퍼티 파일 이용
- 프로퍼티 파일은 텍스트 파일 포맷이며, ‘키=값’ 형태로 기술
db.username=spring
db.password=book
- 위와 같이 databse.properties 파일에 저장되어 있다면 Properties를 이용해 읽기 가능
Properties p = new Properties();
p.load(new FileInputStream("databse.properties"));
- 스프링에선
<util:properties>
전용 태그를 이용해 프로퍼티 파일의 내용을 읽어 초기화된 Properties 타입의 빈을 다음과 같이 정의
<util:properties id="dbProperties" location="database.properties" />
- 또는 프로퍼티 파일을 읽어서 프로퍼티 키에 대응되는 치환자를 찾아 빈의 프로퍼티 값을 업데이트해주는 기능이 있는
<context:property-placeholder>
를 사용
<context:property-placeholder location="database.properties" />
- Properties가 기본적으로 지원하는 프로퍼티파일은 ISO-8859-1 인코딩만을 지원하기 떄문에 영문만 사용 가능
name=토비
name=\uD10\uBE44
- 인코딩으로 표현 불가능한 문자는 u로 시작하는 유니코드 값을 대신 사용
- 한글을 사용할떄는 XML 포맷의 프로퍼티 파일응 사용, Properties는 loadFromXML()을 이용
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="name">토비</entry>
</properties>
- XML 프로퍼티 파일은 UTF-8로 인코딩해서 저장
<util:properties>
나<context:property-placeholder>
에서 모두 두가지 포맷을 지원
스프링에서 사용되는 프로퍼티의 종류
- 프로퍼티 파일 외에도 프로퍼티 값을 지정하고 가져오난 다양한 방법을 지원
환경변수
- 애플리케이션이 구동되는 OS의 환경변수도 키와 값으로 표현되는 대표적 프로퍼티
- 자바에서는 System.getEnv()로 환경변수를 담은 프로퍼티 맵을 제공
- 매우 넓은 범위에 적용되는 프로퍼티
- 시스템 관리자가 서버나 OS 레벨에 동일한 값을 가진 프로퍼티를 넣어 그 위에서 동작하는 모든 WAS의 모든 애플리케이션에 전달해야 한다면 사용을 고려
시스템 프로퍼티
- JVM 레벨에 정의된 프로퍼티를 의미
- JVM이 시작될 떄 시스템 관련 정보(
os, name, user 등
)부터 자바 관련 정보(java.home, java.version, .java.class.path 등
), 기타 JVM 관련 정보를 프로퍼티에 등록
- 시스템 프로퍼티는 JVM을 시작할 때 -D로 지정한 커맨드라인 옵션도 포함
- WAS 단위로 동일한 활성 프로파일을 지정하고 싶다면 -D 옵션을 이용해 지정
- 코드에서
System.getProperties()
로 사용 가능 타입은 Properties
JNDI
- WAS에 여러개의 웹 애플리케이션이 올라가고 그중 하나의 애플리케이션에만 프로퍼티를 지정하고 싶다면 JNDI 프로퍼티 또는 JNDI 환경 값 사용하는 방법을 고려
- 주로 DataSource 풀 같은 리소스를 바인딩해두고 이를 애플리케이션에서 가져와 사용하는 방법을 주로 사용
- 단순한 프로퍼티 값을 지정할 떄도 이용 가능하며 JNDI 값을 코드로 작성하려면 제법 복잡하지만 스프링에선 다음과 같은 전용 태그로 사용 가능
<jee:jndi-lookup id="db.username" jndi-name="db"username" />
서블릿 컨텍스트 파라미터
- 웹 애플리케이션 레벨의 프로퍼티를 지정하고 싶고, JNDI 값을 설정하기 번거롭다면 web.xml에 서블릿 컨텍스트 초기 파라미터를 프로퍼티로 사용 가능
<context-param>
<param-name>db.username</param-name>
<param-value>spring</param-value>
</context-param>
- 스프링에선 두 가지 방법을 이용해 이 프로퍼티 값을 사용
ServletContext
오브젝트를 직접 빈에서 주입 받은 뒤,ServletContext
를 통해 컨텍스트 파라미터를 가져오는 방법ServletContextAware
인터페이스를 빈에 사용하거나 다음과 같이@Autowired
를 사용하면 서블릿 컨텍스트 사용 가능
@Autowired ServletContext servletContext;
servletContext.getInitParameter()
를 통해<context-param>
사용 가능
ServletContextPropertyPlaceholderConfigurer
를 사용PropertyPlaceholderConfigurer
의 서블릿 컨텍스트 파라미터 버전
- 장점 : 웹 애플리케이션 레벨에 프로퍼티를 둘 떄 유용
- 단점 : web.xml이 웹 애플리케이션에 포함되어 있기 때문에 환경에 따라 값을 따로 지정하고 사용하기 번거로움
서블릿 컨픽 파라미터
- 서블릿 컨텍스트(ServletContext)와 서블릿 컨픽(ServletConfig)은 혼동하기 쉬움
- ServletContext : 서블릿이 소속된 웹 애플리케이션의 컨텍스트
- ServletConfig: 개별 서블릿을 위한 설정
- 서블릿 컨텍스트가 서블릿 컨픽보다 범위가 넓음
- 서블릿 컨텍스트는 특정 서블릿에 소속되지 않은 루트 컨텍스트에도 영향을 주지만, 서블릿 컨픽은 해당 서블릿의 서블릿 컨텍스트에만 영향을 줌
- 위에 방법들 중에 가장 좁은 적용 범위
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
...
<init-param>
<param-name>temp.folder</param-name>
<param-value>/tmp</param-value>
</init-param>
...
</servlet>
- 서블릿 컨픽 프로퍼티에 접근하는 방법은 서블릿 컨텍스트와 유사
ServletConfigAware
인터페이스를 구현하거나@Autowired
로 주입받아서getInitParameter()
를 사용
- 이 외에도 자바 코드에서 직접 프로퍼티 정보를 생성하는 방법
- DB에서 프로퍼티 정보를 읽어오거나 원격 서버에 있는 프로퍼티 정보를 웹 서비스나 REST를 이용해 가져오는 방법이 존재
프로파일의 통합과 추상화
- 스프링 3.0까지는 프로퍼티 종류를 저장해두는 방식이 달라지면 사용하는 방법도 다름
- 프로퍼티 파일에 담긴 정보는
<context:property-placeholder>
를 사용
- JNDI로 변경하면
<jee:jndi-lookup>
을 사용하고 치환자 대신 빈 레퍼런스 사용
- 시스템 프로퍼티로 바뀌면 SpEL을 사용해서 가져오거나
@PostConstruct
초기화 메소드에서@Resource
로 가져온systemProperties
빈의 메소드를 사용
- 프로퍼티 파일에 담긴 정보는
- 스프링 3.1에서는 프로퍼티 소스라는 개념을 추상화하고, 프로퍼티의 저장 위치에 상관없이 동일한 API를 이용해 가져올 수 있도록 변경 됌
- 프로퍼티 소스는 프로파일과 함께 런타임 환경정보를 구성하는 핵심 정보
Environment
타입의 런타임 오브젝트를 이용하면 일관된 방식으로 프로퍼티 정보를 가져 오는게 가능
StandardEnvironment
는GenericXmlApplicationContext
나AnnotationConfigApplicationContext
처럼 독립형 애플리케이션용 컨텍스트에서 사용되는 런타임 환경 오브젝트
StandardEnvironment
는 기본적으로 두가지 종류의 프로퍼티 소스를 제공- 시스템 프로퍼티 소스
- 환경변수 프로퍼티 소스
- 애플리케이션에서 이 두가지 종류의 프로퍼티 소스로부터 프로퍼티를 찾고싶으면 다음과같이
Environment
오브젝트의getProperty()
를 사용AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(...); System.out.println(ac.getEnvironment().getProperty("os.name")); // 시스템 프로퍼티 System.out.println(ac.getEnvironment().getProperty("Path")); // 환경변수
- 코드에선 어던 종류의 프로퍼티 소스에 담긴것이지 신경 쓸 필요가 없어짐
- 두 개 이상의 프로퍼티 소스를 갖고 있을 때 양쪽에 동일한 키의 프로퍼티가 존재하는 경우 프로퍼티 소스의 우선순위를 따라 우선순위가 높은 프로퍼티 소스의 프로퍼티가 사용
- 우선순위 : 시스템 프로퍼티 > 환경변수
- 프로퍼티 소스를 코드에 직접 추가도 가능
Map<String, Object>
타입의 소스를 지원하는MapPropertySource
Properties
타입의 소스를 지원하는PropertiesPropertySource
Properties p = new Properties(); p.put("db.username", "spring"); PropertySource<?> ps = new PropertiesPropertySource("customPropertySource", p);
- 위에 소스는 애플리케이션 컨텍스트의 환경 오브젝트에 다음과 같이 추가
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(...); PropertySource<?> ps = ... // 프로퍼티 소스 생성 ac.getEnvironment().getPropertySources().addFirst(ps);
- 프로퍼티 소스를 환경 오브젝트에 직접 추가할 때는 우선순위를 함께 지정이 필요
- addFirst()로 등록하면 현재 등록된 프로퍼티 소스보다 우선순위가 높게 지정
- addLast()로 하면 가장 낮은 우선순위로 지정
- addBefore(), addAfter()를 이용해 특정 프로퍼티 소스를 기준으로 우선순위 지정 가능
- 자바 코드에서 프로퍼티 소스를 추가가 가능하므로 필요한 경우 DB에서 프로퍼티 정보를 가져오거나 원격 서버로부터 프로퍼티 값을 읽어오는 등의 작업도 가능
프로퍼티 소스의 사용
Environment.getProperty()
- 가장 간단한 방법은 Environment를 빈에 주입받아서 직접 프로퍼티 값을 가져오는 방법
@Autowired Environment env;
- env.getProperty()를 이용하면 주어진 키에 해당하는 프로퍼티 값을 돌려 받음
String serverOS = env.getProperty("os.name");
- 해당 빈에서 반복적으로 사용해야한다면 @PostConstruct를 이용해 클래스 멤버 필드에 미리 프로퍼티 값을 저장해두는 편이 좋음
@Autowired Environment env;
private String adminEmail;
@PostConstruct
public void init() {
this.admninEmail = env.getProperty("admin.email");
}
- DataSource 빈을 만들 때 필요한 프로퍼티 정보를 주입해주는 방법
- @Configuration 클래스에서 @Bean 메소드로 DataSource 빈을 만든다면 아래와 같이 주입
@Bean public DataSource dataSource() { BasicDataSource ds = new BasicDataSource(); ds.setUsername(env.getProperty("db.username")) ... }
PropertySourceConfigurerPlaceholder와 <context:property-placeholder>
- @PostConstruct를 사용해 코드를 넣어주는 것이 번거롭게 느껴진다면 @Value와 프로퍼티 ${}치환자를 사용
@Value("${db.username}") private String username;
- @Value에 치환자를 사용하려면 컨텍스트에
PropertySourcePlaceholderConfigurer
빈이 등록되어 있어야 함
PropertySourcePlaceholderConfigurer
는 XML에서 프로퍼티 파일의 정보를 프로퍼티 치환자에 넘겨줄 때 사용했던PropertyPlaceholderConfigurer
와 유사하지만 동작방식과 기능이 다름PropertyPlaceholderConfigurer
나 스프링 3.0에서 사용하던<context:property-placeholder>
는 프로퍼티 파일을 가져와 XML 파일에 ${} 치환자를 프로퍼티 값으로 바꿔주는 기능을 담당
- 반면
PropertySourcePlaceholderConfigurer
는 특정 프로퍼티 파일이 아니라 환경 오브젝트에 통합된 프로퍼티 소스로부터 프로퍼티 값을 가져와 컨텍스트의 @Value 또는 XML에 있는 ${} 치환자의 값을 바꿔줌
- PropertySourcePlaceholderConfigurer에는 프로퍼티 파일을 지정하지 않아도 됌
@Bean
public static PropertySourcePlaceholderConfigurer pspc() {
return new PropertySourcePlaceholderConfigurer();
}
- 빈을 등록할 떄는 @Bean 메소드를 반드시 static 메소드로 등록
- @Bean 메소드는 final이 아닌 public 접근 제한자를 가진 인스턴스를 빈으로 생성
PropertySourcePlaceholderConfigurer
는 예외적으로 static 메소드를 이용해서 생성- 이유는
PropertySourcePlaceholderConfigurer
가BeanFactoryPostProcessor
후처리기로 되어 있는데 @Bean 메소드를 처리하는 기능도BeanFactoryPostProcessor
로 되어 있어서 Bean 메소드에서 다른 후처리기를 만들어서 다시 @Bean 메소드가 있는 클래스의 빈 설정을 가공하도록 만들 수 없기 때문
- 스프링은 빈 후처리기가 만들어지기 전에 static으로 정의된 빈을 만들어 문제 해결
- 이유는
- XML 설정만 사용하거나 XML 설정과 @Configuration을 함께 사용하는 경우
<context:property-placeholder>
전용 태그를 사용해도 됌- 스프링 3.0에선 특정 프로퍼티 파일만을 처리하는
PropertyPlaceholderConfigurer
를 등록, 따라서 location 애트리뷰트로 프로퍼티 파일 지정이 필요
- 스프링 3.1에선
<context:property-placeholder>
는 등록된 모든 프로퍼티 소스로부터 프로퍼티를 가져와서 치환자에 넣어주는PropertySourcePlaceholderConfigurer
빈이 등록
- 스프링 3.0에선 특정 프로퍼티 파일만을 처리하는
- location을 포함한
<context:property-placeholder>
는 스프링 3.0에서 작성된 XML을 호환성을 유지하면서 사용할때나 스프링 3.1이지만 XML만 사용하는 경우에 치환자를 적용하고 싶을때만 사용하고 그 외에는 사용을 피함을 추천
@Configuration
클래스를 함께 사용하는 경우라면@Value
의 치환자에도 적용되므로PropertySourcePlaceholderConfigurer
빈을 중복해서 등록해줄 필요 없음
PropertySourcePlaceholderConfigurer
는 기존PropertyPlaceholderConfigurer
와 달리 부모 컨텍스트의 프로퍼티도 사용 가능, Environment를 이용해 프로퍼티를 가져오는 경우도 사용 가능
@PropertySource와 프로퍼티 파일
- 프로퍼티 파일도 프로퍼티 소스로 등록하고 사용 가능, @PropertySource를 이용
- @PropertySource를 @Configuration 클래스에 붙이고 프로퍼티 파일 위치를 기본 엘리먼트 값으로 넣어주면 사용 가능
@Configuration
@PropertySource("database.properties")
public class AppConfig {
- 프로퍼티 파일을 여러 개 동시 지정이 가능하며, 이름도 넣을 수 있음
@PropertySource(name="myPropertySource", value={"database.properties", "settings.xml"})
- @PropertySource로 등록되는 프로퍼티 소스는 컨텍스트에 기본적으로 등록되는 프로퍼티 소스보다 우선순위가 낮음
웹 환경에서 사용되는 프로퍼티 소스와 프로퍼티 소스 초기화 오브젝트
- 루트, 서블릿 웹 컨텍스트에 의해 만들어지는 웹 애플리케이션 컨텍스트는
StandardServletEnvironment
타입의 런타임 환경 오브젝트를 사용
StandardServletEnvironment
는StandardEnvironment
가 등록해주는환경변수, 시스템
프로퍼티 소스에 더해서JNDI, 서블릿, 서블릿 컨픽
프로퍼티 소스를 추가로 등록
- 프로퍼티 소스의 우선순위
- 서블릿 컨픽 프로퍼티
- 서블릿 컨텍스트 프로퍼티
- JNDI 프로퍼티
- 시스템 프로퍼티
- 환경변수 프로퍼티
- 활성 프로파일을 지정하는 방법을 설명할 때 소개했떤 다섯가지 방법과 동일
- 활성 프로파일 정보도
spring.profiles.active
키를 가진 프로퍼티를 찾아서 사용
- 서블릿 환경에서도 프로퍼티 파일을 프로퍼티 소스로 추가하려면 @PropertySource를 사용하면 되는데 우선순위가 가장 낮음
- 코드를 이용해 프로퍼티 소스를 추가하는 방법
- 웹 환경에서는 리스너나 서블릿에서 컨텍스트가 자동으로 생성
- 이렇게 생성되는 애플리케이션 컨텍스트에 스프링 3.1에 새롭게 추가된 애플리케이션 컨텍스트 초기화 오브젝트를 사용
public interface ApplicationContextInitializer <C extends ConfigurableApplicationContext> { void initialize(C applicationContext); }
- 컨텍스트가 생성된 후에 초기화 작업을 진행하는 오브젝트를 만들때 사용
- 대부분 빈 메타정보는 XML이나 @Configuration 클래스로 작성할 수 있기 때문에 별도의 초기화 과정이 필요 없음
- 환경 오브젝트나 그에 포함되는 프로퍼티 소스는 빈이 아니고 컨텍스트가 생성하는 오브젝트이기 떄문에 설정을 위해 별도의 과정이 필요
public class MyContextInitializer implements ApplicationContextInitializer<AnnotationConfigWebApplicationContext> { @Override public void initialize(AnnotationConfigWebApplicationContext ac) { ConfigurableEnvironment ce = ac.getEnvironment(); Map<String, Object> m = new HashMap<>(); m.put("db.username", "spring"); ce.getPropertySources().addFirst(new MapPropertySource("myPS", m)); } }
- 컨텍스트 초기화 오브젝트는
contextInitializerClasses
컨텍스트 파라미터로 지정, 루트 컨텍스트라면 다음과 같이<context-param>
을 이용
<context-param> <param-name>contextInitializerClasses</param-name> <param-value>MyContextInitializer</param-value> </context-param>
- 서블릿 컨텍스트의 초기화에 사용하려면 <servlet>에 <init-param>을 이용해 지정
<init-param> <param-name>contextInitializerClasses</param-name> <param-value>MyContextInitializer</param-value> </init-param>
- 컨텍스트 초기화 오브젝트는 컨텍스트에 등록된 환경 오브젝트를 수정하거나 프로퍼티 소스를 추가하는 등 다양한 초기화 작업에 사용 가능
- 빈 설정 메타정보나 프로퍼티 지정 방법으로 가능한 작업에 컨텍스트 초기화 오브젝트를 이용하는 것은 바람직하지 않음
'개발서적 > 토비 스프링 3.1-Vol.2' 카테고리의 다른 글
[토비의 스프링 - Vol.2] 2장 - 2.2 JDBC (0) | 2022.01.17 |
---|---|
[토비의 스프링 - Vol.2] 2장 - 2.1 공통개념 (0) | 2022.01.17 |
[토비의 스프링 - Vol.2] 1장 - 1.5.4 런타임 환경 추상화와 프로파일 (0) | 2022.01.17 |
[토비의 스프링 - Vol.2] 1장 - 1.5.3 웹 애플리케이션의 새로운 IoC 컨테이너 구성 (0) | 2022.01.17 |
[토비의 스프링 - Vol.2] 1장 - 1.5.2 컨테이너 인프라 빈을 위한 자바 코드 메타정보 (0) | 2022.01.17 |