본문 바로가기

Spring/Spring

Spring Environment 스프링 애플리케이션의 환경에 접근하여 설정 값을 얻어오는 방법(feat. ApplicationContext의 다른 기능, profiles, @Profile)

반응형

Environment

스프링(Spring)에서 제공하는 인터페이스 중 Environment에 대해서 알아볼 예정이다.

Environment 는 말 그대로 스프링 환경이자 설정과 관련된 인터페이스다. (구글에 검색하면 Environment의 동의어로 Setting이 있다.)

어떤 환경 설정에 접근할 수 있는 인터페이스일까?

바로 Profiles 설정과 Property 설정에 접근할 수 있다.

아까부터 포스트의 제목을 포함해서 자꾸 접근한다고 하는데, 그 이유는 설정 값을 변경하는 건 불가능하고 접근해서 값을 가져올 수만 있기 때문이다. (ConfigurableEnvironment 인터페이스로 캐스팅하여 설정 값을 추가할 순 있지만 Environment 인터페이스로는 불가능하다.)

ApplicationContext의 또 다른 기능

EnvironmentApplicationContext 의 또 다른 기능이다.

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    //...
}

ApplicationContext 인터페이스를 보면 위와 같이 되어있다. 그 중에 EnvironmentCapable 라는 인터페이스를 부모 인터페이스로 두고 있는 것을 확인할 수 있는데 이게 바로 ApplicationContext 가 DI(Dependency Injection) 관점에서 기능 외의 또 다른 기능인 Environment의 기능을 갖는 것이다.

public interface EnvironmentCapable {
    Environment getEnvironment();
}

위에서 설명한 EnvironmentCapable 인터페이스다. getEnvironment() 를 통해서 Environment에 접근할 수 있다.

각설하고 앞서 말했던 Profiles 설정과 Property 설정에 접근하는 Environment 를 확인해보자.


Profiles

Profiles 은 스프링 웹 애플리케이션을 개발할 때, 애플리케이션 환경에 따라 약간씩 변경이 되어야할 설정 값들을 코드의 변경없이 설정만으로 편리하게 변경할 수 있도록 제공하는 인터페이스이자 분리의 기준이라고 할 수 있다.

일반적인 경우에 Profiles 를 사용하는 예제를 보자.

spring:
  profiles:
    active: prod # 어떤 Profiles를 기준으로 설정값을 활성화시킬건지 설정

---
spring:
  profiles: dev # develope 환경일 때 아래 설정을 활성화
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: password
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create
  h2:
    console:
      enabled: true

---
spring:
  profiles: prod # product 환경일 때 아래 설정 활성화
  datasource:
    url: jdbc:mysql://111.222.0.111:3306/office?serverTimezone=UTC&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: jdk
    password: jeongpro
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update

스프링 애플리케이션에서 설정파일(e.g. application.properties 이나 application.yml)에 위와 같이 사용하는게 일반적이다.

위 내용을 대략적으로 설명하면, 개발환경(dev)에서는 h2 메모리 데이터베이스를 사용하고, 실제 제품환경(prod)에서는 mysql 설정을 따르는 것이다.

properties, yaml로 설정

spring:
  profiles:
    active: hello
notification:
  target: default@office.co.kr

---
spring:
  profiles: test
notification:
  target: test@office.co.kr

---
spring:
  profiles: local
notification:
  target: jeongpro@naver.com

---
spring:
  profiles: product
notification:
  target: support@office.co.kr

.properties 파일이나 .yml 파일로도 Profiles 을 설정할 수 있다.

위의 예제도 application.yml 파일의 일부다.

Profiles 로 test, local, product 그리고 default 환경을 만들었다.

참고로 spring.profiles.active 값이 "hello"라고 되어있는데 이럴 때는 매핑되는 profiles 를 찾을 수 없기 때문에 default 설정을 따른다. 따라서 예제로 만든 notification.target 의 값은 "default@office.co.kr"로 나올 것이다. (아래 예제를 통해서 확인할 예정이다.)

결론적으로 Profiles 에 대한 설정은 .properties, .yml 파일에서 할 수 있다는 얘기다.

VM Option로 설정

JVM(Java Virtual Machine) 옵션으로도 Profile을 설정할 수 있다.

-Dspring.profiles.active=local,test

위와 같이 명령어로 옵션을 추가하게 되면 Profiles 로 지정할 환경으로 local, test로 설정할 수 있다.

Profiles 값이 여러 개가 활성화될 수 있다는 것을 알아둔다.

결론적으로 Profiles 에 대한 설정은 VM Option으로도 할 수 있다는 얘기다.


Profiles에 따른 사용

EnvironmentProfiles 에 접근하여 사용할 수 있으므로 자꾸 Profiles 에 대한 얘기를 할 수 밖에 없다.

앞서 Profiles 가 어디서 설정되는지를 알아봤으니 이제 Profiles 가 설정되었을 때 어떻게 사용되는지를 확인해본다.

@Profile

@ProfileProfiles 설정에 따라 빈은 생성하고 안하고를 결정할 수 있다.

@Configuration
@EnableCaching
public class CacheConfiguration {
    @Bean
    @Profile("product")
    public CacheManager ehcache(){
        return new EhCacheCacheManager();
    }
    @Bean
    @Profile("!product")
    public CacheManager cache(){
        return new ConcurrentMapCacheManager();
    }
}

위와 같이 메소드위에 달아서 Profiles 설정에 따라 어떤 빈을 생성할지를 결정할 수 있다.

@Configuration
@EnableCaching
@Profile("test")
public class CacheConfiguration {
    @Bean
    public CacheManager ehcache(){
        return new EhCacheCacheManager();
    }
}

아니면 위와 같이 클래스 위에 달아서 Profiles 설정에 따라 빈을 생성할지 말지를 결정할 수 있다.

Environment.acceptsProfiles(Profile...)

Environment에서도 Profiles 설정에 따라 다르게 코드를 적용할 수 있다.

.property, .yml 파일로 Profiles 를 설정할 때, 사용한 yml파일을 참고하고 아래의 예제를 통해 사용법을 알아본다.

@RestController
public class EmailController {
    @Autowired
    private Environment environment;
    @Value("${notification.target}")
    private String target;

    @PostConstruct
    private void init(){
        System.out.println("getDefaultProfiles          : " + Arrays.toString(environment.getDefaultProfiles()));
        System.out.println("getActiveProfiles           : " + Arrays.toString(environment.getActiveProfiles()));
        System.out.println("environment.acceptsProfiles  : " + environment.acceptsProfiles(Profiles.of("default", "test")));
        System.out.println("environment.getProperty     : " + environment.getProperty("notification.target"));
        System.out.println("target by @Value annotation : " + target);
    }
}

Environment 인터페이스를 살펴보면 세 가지의 추상메소드가 존재한다.

  • getDefaultProfiles : 기본 적용이된 Profiles 배열을 리턴해주는 메소드
  • getActiveProfiles : 현재 활성화된 Profiles 배열을 리턴해주는 메소드
  • acceptsProfiles : 파라미터로 제공하는 Profiles들이 현재 환경에 적용되어 있는지 여부를 리턴해주는 메소드

위의 메소드를 사용하여 코드를 분기할 수도 있다. 물론 지향하고 권장하는 스타일은 아니지만, 필요에 따라 쓸 수도 있다.

"test"로 Profiles를 활성화하고 위 코드를 테스트를 해본다. 결과는 아래와 같다.

getDefaultProfiles          : [default]
getActiveProfiles           : [test]
environment.acceptsProfiles : true
environment.getProperty     : test@office.co.kr
target by @Value annotation : test@office.co.kr

getActiveProfiles의 결과로 "test"가 활성화되어있으니 acceptsProfiles의 결과가 true 로 나오는 것을 확인할 수 있다.

이번에는 "local, product"로 Profiles를 활성화하고 위 코드를 테스트해본다. 결과는 아래와 같다.

getDefaultProfiles          : [default]
getActiveProfiles           : [local, product]
environment.acceptsProfiles : false
environment.getProperty     : support@office.co.kr
target by @Value annotation : support@office.co.kr

getActiveProfiles의 결과로 "local, product"이 활성화되어있으니 acceptsProfiles의 결과가 false 로 나오는 것을 확인할 수 있다. "test"나 "default"가 있는지 검사했는데 없으니까 그렇게 나온 것이다.

특이사항은 아래에서 설명할 Property 에 접근하는 기능이다.

"local" Profiles에서는 notification.target 의 값이 "jeongpro@naver.com"인데, "product" Profiles도 있으니까 notification.target 값이 "support@office.co.kr"로 나왔다.

.properties, .yml 파일에서 Profiles 설정이 중복될 경우 활성화한 Profiles에서 나중에오는게 우선된다.

현재는 spring.profiles.active 값이 local, product 이라고 순서대로 해놨는데 product, local 이렇게 해놨으면 반대로 local의 값이 덮어씌워진다.

PropertyResolver

Environment 인터페이스는 부모 인터페이스로 PropertyResolver 를 갖고 있다.

이 인터페이스에서 프로퍼티에 접근하게 해주기 때문에 Environment에서도 프로퍼티에 접근할 수 있다.

위에서 사용한 예시에서 아래와 같은 부분이 있었다.

System.out.println("environment.getProperty     : " + environment.getProperty("notification.target"));

environment 의 getProperty() 메소드를 통해서 프로퍼티 값에 접근할 수 있다.

그리고 굳이 Environment 로만 프로퍼티 값을 가져오는 건 아니다.

@PropertySource 도 있고, @ConfigurationProperties 도 있다. (@ProperySource는 권장하는 방법이 아니다. @ConfigurationProperties가 권장된다.

또 다른 방법으로 @Value 도 있다. 위의 예제에서도 @Value 를 통해서 프로퍼티에 접근한 것을 알 수 있다.

어찌됐든 Environment 에 대해서 알아보는 시간이었으니, Environment 를 통해서 프로퍼티를 가져올 수 있다는 것을 알아봤다.


해결하고자 한 문제

이거는 필자 추즉이지만, Environment 에 직접 접근해서 뭔가 할 이유가 없기는 하다.

굳이 해결하기 위한 문제로는 Profiles 설정에 따라 코드가 분기해야할 경우같다.

예를들어, 메소드 전체가 아니라 일부에서 Profiles 에 따라 구분되어야 한다면 어떻게 해야할까?

분기되어야 하는 코드를 또 다른 메소드로 빼고 @Profiles 를 적용하면 될까? @Profiles가 적용되어 있어도 메소드를 직접 호출하는 것은 막을 수 없다.

메소드를 작게 구분하는 것도 당연히 좋지만 그렇다고 Profiles 를 코드에서 구분하는 방법은 없다. 그 때 Environment 를 사용하면 해결될 것이다. acceptsProfiles() 메소드를 이용해서 말이다.

반응형