Properties 상속으로 공통 설정과 서브프로젝트 설정 분리 프로그래밍

Java/Spring 애플리케이션을 만들다보면 항상 하는 것 중에 하나가 properties 파일로 설정 정보를 만드는 것이다.

이 중에 자주 나오는 패턴이 있는데, 여러 프로젝트가 묶여 있는 경우(Maven 에서 Multi Module Project)가 상당히 많고 이 경우 공통 설정 프라퍼티 파일과 각 서브 프로젝트의 설정 프라퍼티가 분리돼서 존재하게 되는 것이다(안 그렇다면 아마도 공통 설정을 여러 프로젝트에 반복해서 넣고 있을 것이고 이는 공통 설정을 삐끗하여 한 프로젝트에서 안 고쳐서 오류가 발생하는 실수를 많이 한다는 뜻이다).

내가 참여하는 프로젝트가 대부분 공통 설정이 있고, 프로젝트별 설정이 또 존재하는 멀티 모듈 프로젝트이다보니 이 설정을 좀 더 쉽게 관리하고 싶어져서 spring-properties-inheritance를 만들어보았다. 원래는 블로그에 올릴 생각이 없었는데 안되는 영어로 깃헙에 올려놓다보니 좀 우리말로 좀 더 쉽게 설명해 두고 싶어졌다.

개념은 간단하다. 각 서브 프로젝트의 설정 프라퍼티 파일이 공통 프라퍼티 파일을 상속하는 구조로 만들고, Spring 설정에서는 바로 이 서브 프로젝트의 설정 프라퍼티 파일만 읽어도 부모 프라퍼티의 값들이 딸려오는 구조로 만드는 것이다.

PS. 혹시나 첨언. Spring Framework 3.1 이상에서는 @PropertySource로 여러 프라퍼티를 하나로 묶어서 등록해주는 것이 이미 가능하다. 굳이 이거 사용할 필요 없음.단, ${PlaceHolder}는 이걸 사용해야 될 듯.

여기에 다음과 같은 기능을 더 넣었다.
  1. 다차 상속 : 프라퍼티가 상속한 부모 프라퍼티는 또 다시 자신만의 부모를 가질 수 있다.(단, 부모가 여럿 있을 수는 없다.)
  2. ${placeholder}로 값 대체 지원 : 부모의 프라퍼티 값이나 System 프라퍼티 값을 ${프라퍼티.키}로 읽어서 자식의 프라퍼티 값 설정에 사용할 수 있다. 단, 자기 자신의 프라퍼티에 대한 값 대체는 지원하지 않는다.
  3. 값 오버라이딩 : 부모의 프라퍼티 값을 자식이 오버라이드 할 수 있다.
  4. 당연히 *.xml과 *.properites 형식을 함께 지원한다. 나는 *.properties를 별로 안 좋아한다.
  5. 이렇게 읽은 값은 SpEL로 사용하면 된다. 나는 placeholderConfigurer가 불편하다고 보고 있다.

이를 사용하면 프라퍼티 형태가 다음과 같이 된다.


parent-properties.xml
<properties>
    <entry key="project.name">Spring Properties Inheritance</entry>
    <entry key="project.home">${java.io.tmpdir}/project</entry>
    <entry key="key.dir">${java.io.tmpdir}/keys</entry>
</properties>


child-properties.xml
<properties>
    <entry key="__extends__">classpath:/parent-properties.xml</entry>
    <entry key="project.name">Sub project</entry> <!-- override parent's project.name -->
    <entry key="subproject.home">${project.home}/subproject</entry>
    <entry key="userproject.home">${project.home}/users/${user.name}</entry> <!-- user.name from System Properties -->
</properties>

바로 __extends__ 프라퍼티를 통해 부모 프라퍼티를 지정해주면 부모 프라퍼티의 값이 모두 함께 들어오며 ${값대체}도 이뤄지게 된다.

값 대체는 부모 프라퍼티 값이 우선이고 그 다음이 System 프라퍼티이다.


이를 스프링에서 사용할 때는 다음과 같이한다.


<bean id="configurationProperties" class="kr.pe.kwonnam.properties.InheritablePropertiesFactoryBean">
    <property name="location" value="classpath:/child-properties.xml" />
</bean>

사용은 그냥 java.util.Properties 객체이므로 SpEL(#{configurationProperties['project.name']})이나, 실제 코드에서 값을 직접 getProperty("키")로 호출해서 얻으면 된다.


덧글

  • 몽몽이 2013/07/28 22:23 #

    근본적으로는 설정 로딩의 디자인 패턴이 Manager 유형인게 마음에 안 듭니다.
    왜 이거야말로 Adaptor 패턴을 사용하지 않았을까요? Manager 구조를 유지하더라도 직접 설정값에 I/O하는 부분은 Adaptor 패턴으로 재구현되었으면 좋겠습니다. 사실 설정을 텍스트 파일이 아닌 다른 곳에서 관리하는 것이 더 적합한 경우가 많지 않습니까?
  • 권남 2013/07/28 22:29 #

    정확히 Adaptor로 가게 되면 어떤 구조가 되는지 잘 모르겠습니다.
    그런데, Spring은 3.1 버전 이상에서 파일이 아닌 모든 원하는 곳에서(프라퍼티 파일, JNDI, 그 외 키=값쌍으로 구성할 수 있는 모든 곳)에서 설정을 읽어 들일 수 있는 구조로 돼 있습니다. 아마도 그것을 말씀하시는 듯 하네요.
※ 로그인 사용자만 덧글을 남길 수 있습니다.