관리 메뉴

나구리의 개발공부기록

외부설정과 프로필2, 프로젝트 설정, 외부 설정 사용(Environment/@Value/@ConfigurationProperties(시작/생성자/검증)), YAML, @Profile 본문

인프런 - 스프링 완전정복 코스 로드맵/스프링 부트 - 핵심 원리와 활용

외부설정과 프로필2, 프로젝트 설정, 외부 설정 사용(Environment/@Value/@ConfigurationProperties(시작/생성자/검증)), YAML, @Profile

소소한나구리 2024. 12. 6. 00:18

출처 : 인프런 - 스프링 부트 - 핵심 원리와 활용(유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 프로젝트 설정

1) 프로젝트 설정

(1) 프로젝트 설정 - build.gradle

  • 제공된 프로젝트를 사용
  • 스프링 부트로 프로젝트를 생성하고 롬복 라이브러리를 사용하고 테스트에서 롬복을 사용할 수 있도록 설정
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    //test lombok 사용
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
    useJUnitPlatform()
}

2. 외부 설정 사용 - Environment

1) Environment

(1) 다양한 외부 설정 읽기

  • 스프링은 Environment는 물론이고 Environment를 활용해서 더 편리하게 외부 설정을 읽는 방법들을 제공함
  • 1. Environment
  • 2. @Value - 값주입
  • 3. @ConfigurationProperties - 타입 안전한 설정 속성

(2) MyDataSource

  • datasource패키지를 생성 후 작성
  • url, username, password: 접속 url, 이름, 비밀번호
  • maxConnection: 최대 연결 수
  • time: 응답 지연시 타임아웃
  • options: 연결시 사용하는 기타 옵션들
  • @PostConstruct에서 확인을 위해 설정된 값을 출력
package hello.datasource;

@Slf4j
public class MyDataSource {

    private String url;
    private String username;
    private String password;
    private int maxConnections;
    private Duration timeout;
    private List<String> options;

    public MyDataSource(String url, String username, String password, int maxConnections, Duration timeout, List<String> options) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.maxConnections = maxConnections;
        this.timeout = timeout;
        this.options = options;
    }

    @PostConstruct
    public void init() {
        log.info("url={}", url);
        log.info("username={}", username);
        log.info("password={}", password);
        log.info("maxConnections={}", maxConnections);
        log.info("timeout={}", timeout);
        log.info("options={}", options);
    }
}

 

(3) application.properties

  • 외부속성은 설정 데이터(application.properties)를 사용
  • 별도의 프로필은 사용하지 않음
my.datasource.url=local.db.com
my.datasource.username=username
my.datasource.password=password
my.datasource.etc.max-connection=1
my.datasource.etc.timeout=3500ms
my.datasource.etc.options=CACHE,ADMIN

 

** 참고 - properties 캐밥 표기법

  • properties는 자바의 낙타 표기법(maxConnection)이 아니라 소문자와 -(dash)를 사용하는 캐밥표기법(max-connection)을 주로 사용함
  • 이곳에 낙타 표기법을 사용한다고 해서 문제가 되는 것은 아니지만 스프링은 properties에 캐밥 표기법을 권장함

(4) MyDataSourceEnvConfig

  • config패키지를 생성후 작성
  • MyDataSource를 스프링빈으로 등록하는 자바 설정
  • Environment를 사용하여 외부의 설정의 종류와 관계없이 코드 안에서 일관성 있게 외부 설정을 조회
  • env.getProperty(key, Type): 호출할 때 타입 정보를 주면 해당 타입으로 변환을 해줌(스프링 내부 변환기가 동작함)
    - getProperty("...", Integer.class): 문자 -> 숫자로 변환
    - getProperty("...", Duration.class): 문자 -> 기간으로 변환
    - getProperty("...", List.class): 문자 -> List로 변환
  • 작성 후 애플리케이션을 실행해보면 MyDataSource가 스프링 빈으로 등록되어 외부 설정값이 조회되는 코드가 실행되고 init()메서드를 통해 설정값들이 출력되는 것을 확인할 수 있음
package hello.config;

@Slf4j
@Configuration
public class MyDataSourceEnvConfig {

    private final Environment env;

    public MyDataSourceEnvConfig(Environment env) {
        this.env = env;
    }

    @Bean
    public MyDataSource myDataSource() {
        String url = env.getProperty("my.datasource.url");
        String username = env.getProperty("my.datasource.username");
        String password = env.getProperty("my.datasource.password");
        int maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
        Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);
        List<String> options = env.getProperty("my.datasource.etc.options", List.class);

        return new MyDataSource(url, username, password, maxConnection, timeout, options);
    }
}

 

** 참고

(5) ExternalReadApplication - 수정

  • @Import(설정파일.class)로 설정파일을 추가하고, @SpringBootApplication(scanBasePackages = "hello.datasource")를 적용하여 hello.config를 컴포넌트 스캔 대상에 등록되지 않도록 설정
  • 계속 설정 파일을 추가하고 해당 설정파일만 스프링 빈으로 등록되도록 실행하면서 실습을 진행하기 위함
  • 이렇게 하지 않으면 @Configuration이 붙은 모든 설정파일이 전부 스프링 빈으로 등록되어 버림
@Import(MyDataSourceEnvConfig.class)
@SpringBootApplication(scanBasePackages = "hello.datasource")
public class ExternalReadApplication {
    // ... 기존 코드 동일
}

 

(6) 정리 및 단점

  • application.properties에 필요한 외부 설정을 추가하고 Environment를 통해서 해당 값들을 읽어서 MyDataSource를 만들었음
  • 향후 외부 설정 방식이 달라져도 애플리케이션 코드를 그대로 유지할 수 있음
  • 그러나 이방식은 Environment를 직접 주입받고 env.getProperty(key)를 통해서 값을 꺼내는 과정을 반복해야함 즉, 귀찮음!
  • 스프링은 @Value를 통해서 외부 설정값을 주입받는 더욱 편리한 기능을 제공함

3. 외부 설정 사용 - @Value

1) @Value

(1) MyDataSourceValueConfig

  • @Value를 사용하면 외부 설정값을 편리하게 주입받을 수 있으며 @Value도 내부에서는 Environment를 사용함
  • @Value에 ${}를 사용하여 외부 설정의 키 값을 주면 원하는 값을 주입 받을 수 있음
  • myDataSource1(): 필드에 주입 받은 설정값을 사용
  • myDataSource2(): 파라미터를 통해서 설정 값을 주입받음
package hello.config;

@Slf4j
@Configuration
public class MyDataSourceValueConfig {

    @Value("${my.datasource.url}")
    private String url;
    @Value("${my.datasource.username}")
    private String username;
    @Value("${my.datasource.password}")
    private String password;
    @Value("${my.datasource.etc.max-connection}")
    private int maxConnections;
    @Value("${my.datasource.etc.timeout}")
    private Duration timeout;
    @Value("${my.datasource.etc.options}")
    private List<String> options;

    @Bean
    public MyDataSource myDataSource1() {
        return new MyDataSource(url, username, password, maxConnections, timeout, options);
    }

    @Bean
    public MyDataSource myDataSource2(
            @Value("${my.datasource.url}") String url,
            @Value("${my.datasource.username}") String username,
            @Value("${my.datasource.password}") String password,
            @Value("${my.datasource.etc.max-connection}") int maxConnections,
            @Value("${my.datasource.etc.timeout}") Duration timeout,
            @Value("${my.datasource.etc.options}") List<String> options) {
        
        return new MyDataSource(url, username, password, maxConnections, timeout, options);
    }
}

 

(2) ExternalReadApplication 수정 및 실행

  • 기존 @Import를 주석처리하고 @Import(MyDataSourceValueConfig.class)를 추가 후 애플리케이션을 실행하면 @Value로 외부 설정값을 조회하는 것을 확인할 수 있음

(3) 기본값

  • 만약 키를 찾지 못할 경우 코드에서 기본값을 사용하려면 아래처럼 :뒤에 기본값을 적어주면 됨
  • 해당 값을 적용하고 설정 데이터에 username과 max-connection을 주석처리한 후 실행해보면 기본값이 반영된 것을 출력값으로 확인할 수 있음
@Value("${my.datasource.username:my_user}")
@Value("${my.datasource.etc.max-connection:3333}")

 

(4) 단점

  • @Value를 사용하는 방식도 좋지만 하나하나 외부 설정 정보의 키 값을 입력받고 주입 받아와야 하는 부분이 번거로움
  • 설정 데이터를 보면 하나하나 분리 되어있는 것이 아니라 정보의 묶음으로 되어있는데(ex: my.datasource) 이런 부분을 객체로 변환해서 사용할 수 있다면 더욱 편리할텐데, 이것도 스프링이 이미 구현해 놓았음

4. 외부 설정 사용 - @ConfigurationProperties

1) 시작

(1) Type-safe Configuration Properties

  • 스프링은 외부 설정의 묶음 정보를 객체로 변환하는 기능을 제공하는데 이것을 타입 안전한 설정 속성이라고함
  • 객체를 사용하면 타입을 사용할 수 있어서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고 객체를 통해서 활용할 수 있는 부분이 많아짐
  • 쉽게 이야기해서 외부 설정을 자바 코드로 관리할 수 있는 것이며 설정 정보 자체도 타입을 가지게 됨

(2) MyDataSourcePropertiesV1

    • 외부 설정을 주입 받을 객체를 생성하고 각 필드를 외부 설정의 키 값에 맞추어 준비함
    • @ConfigurationProperties가 있으면 외부 설정을 주입받는 객체라는 뜻이며 여기에 외부 설정 KEY의 묶음 시작점인 my.datasource를 적어주면됨
    • 기본 주입 방식은 자바빈 프로퍼티 방식이므로 Getter, Setter가 필요함(여기서는 @Data를 사용하여 자동 생성되도록 하였음)
package hello.datasource;

@Data
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV1 {

    private String url;
    private String username;
    private String password;
    private Etc etc = new Etc();

    @Data
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options = new ArrayList<>();
    }
}

 

(3) MyDataSourceConfigV1

  • @EnableConfigurationProperties(MyDataSourcePropertiesV1.class): 스프링에게 사용할 @ConfigurationProperties를 지정해주어야 함, 이렇게 하면 해당 클래스는 스프링 빈으로 등록되고 필요한 곳에서 주입 받아서 사용할 수 있음
  • private final MyDataSourcePropertiesV1 properties: 설정 속성을 생성자를 통해 주입 받아서 사용함
package hello.config;

@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 {

    private final MyDataSourcePropertiesV1 properties;

    public MyDataSourceConfigV1(MyDataSourcePropertiesV1 properties) {
        this.properties = properties;
    }

    @Bean
    public MyDataSource myDataSource() {
        return new MyDataSource(
                properties.getUrl(),
                properties.getUsername(),
                properties.getPassword(),
                properties.getEtc().getMaxConnection(),
                properties.getEtc().getTimeout(),
                properties.getEtc().getOptions());
    }
}

 

(4) ExternalReadApplication - 수정 및 실행

  • 기존 @Import문을 주석 처리 후 @Import(MyDataSourceConfigV1.class)를 추가한 뒤 실행해보면 외부 설정값이 정상적으로 조회되어 출력되는 것을 확인할 수 있음

(5) 타입 안전

  • ConfigurationProperties를 사용하면 타입 안전한 설정 속성을 사용할 수 있음
  • maxConnection속성 값을 아무 문자로 입력하고 실행해보면 숫자가 들어와야 하는데 문자가 들어와서 오류가 발생함
  • 실수로 숫자를 입력하는 곳에 문자를 입력하는 문제를 방지를 해주기때문에 타입 안전한 설정 속성이라고 하며 ConfigurationProperties로 만든 외부 데이터는 타입에 대해서 믿고 사용할 수 있음

(6) 표기법 변환

  • maxConnection은 속성과 자바코드가 표기법이 서로 다른데 스프링은 캐밥 표기법을 낙타 표기법으로 중간에서 자동으로 변환해줌
    - application.properties에서는 max-connection, 자바 코드에서는 maxConnection으로 입력되어있지만 잘 작동함

(7) @ConfigurationPropertiesScan

  • @ConfigurationProperties를 하나하나 직접 등록할 때는 @EnableConfigurationProperties를 사용
  • @ComponentScan이 @Component를 자동으로 스캔하는 것처럼 할 수 있는데, @ConfigurationPropertiesScan을 사용하면됨
  • 해당 애노테이션이 적용된 범위와 그 하위의 패키지의 @ConfigurationProperties를 자동으로 등록하며, 원하는 패키지를 지정할 수 도 있음
@Import(MyDataSourceConfigV1.class)
@SpringBootApplication(scanBasePackages = "hello.datasource")
@ConfigurationPropertiesScan
public class ExternalReadApplication {
    // main 메서드 생략
}

// 아래처럼 특정 범위를 지정할 수도 있음(1개도 등록가능)
@ConfigurationPropertiesScan({"hello.config", "hello.datasource"})

 

(8) 문제

  • MyDataSourcePropertiesV1은 스프링 빈으로 등록되어 잘 동작하지만 Setter를 가지고 있기 때문에 누군가 실수로 값을 변경하는 문제가 발생할 수 있음
  • 여기에 있는 값들은 외부 설정값을 사용해서 초기에만 설정되고 이후에는 변경하면 안되므로, Setter를 제거하고 대신에 생성자를 사용하여 중간에 데이터를 변경하는 실수를 근본적으로 방지하는 것이 좋음
  • 이런 문제가 없을 것 같지만 한번 발생하면 정말 잡기 어려운 버그가 만들어짐
  • 대부분의 개발자가 MyDataSourcePropertiesV1 값을 변경하면 안된다고 인지하고 있지만 어떤 개발자가 자신의 문제를 해결하기 위해서 setter를 통해서 값을 변경하게되면 애플리케이션 전체에 심각한 버그를 유발할 수 있음
  • 좋은 프로그램은 제약이 있는 프로그램임

2) 생성자

(1) MyDataSourcePropertiesV2

  • @ConfigurationProperties는 Getter, Setter를 사용하는 자바빈 프로퍼티 방식이 아니라 생성자를 통해서 객체를 만드는 기능도 지원함
  • 생성자를 만들어서 생성자를 통해서 설정 정보를 주입
  • @Getter 롬복만 추가하여 getter를 생성(setter는 없음)
package hello.datasource;

@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {

    private String url;
    private String username;
    private String password;
    private Etc etc;

    public MyDataSourcePropertiesV2(String url, String username, String password, Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }

    @Getter
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}

(2) MyDataSourceConfigV2

  • MyDataSourcePropertiesV2를 적용하고 빈을 등록, 기존 코드와 크게 다르지 않음
package hello.config;

@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV2.class)
public class MyDataSourceConfigV2 {

    private final MyDataSourcePropertiesV2 properties;

    public MyDataSourceConfigV2(MyDataSourcePropertiesV2 properties) {
        this.properties = properties;
    }

    // ... 나머지 코드 동일
   
}

 

(3) ExternalReadApplication - 수정 및 실행

  • 기존 @Import를 주석 처리후 @Import(MyDataSourceConfigV2.class)적용 후 실행하면 생성자를 통해 객체를 생성되어 외부 속성값이 조회되는 것을 확인할 수 있음

(4) MyDataSourcePropertiesV2 - @DefaultValue 적용

  • 해당 속성 값을 찾을 수 없는 경우 기본값을 사용하도록 지정할 수 있음
  • @DefaultValue Etc etc: etc를 찾을 수 없는 경우 Etc 객체를 생성하고 내부에 들어가는 값은 비워둠(각 타입의 기본값으로 초기화)
  • @DefaultValue({"DEFAULT", "DEFAULT2"}): options을 찾을 수 없을 경우 DEFAULT, DEFAULT2라는 이름의 값을 사용함(1개만 적용할 때는 { } 중괄호 제외하고 입력)
package hello.datasource;

@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {

    // ... 기존 코드 동일

    public MyDataSourcePropertiesV2(String url, String username, String password,
                                    @DefaultValue Etc etc) {
        // ... 기존 코드 동일
    }

    @Getter
    public static class Etc {
    
    // ... 기존 코드 동일

        public Etc(int maxConnection, Duration timeout,
                   @DefaultValue({"DEFAULT", "DEFAULT2"}) List<String> options) {

            // ... 기존 코드 동일

        }
    }
}

 

** 참고 - @ConstructorBinding

  • 스프링 부트 3.0 이전에는 생성자 바인딩 시에 생성자에 @ConstructorBinding 애노테이션을 필수로 사용해야 했음
  • 그러나 부트 3.0 이후부터는 생성자가 하나일 때는 해당 애노테이션을 생략할 수 있고, 생성자가 둘 이상인 경우에는 적용할 생성자에 해당 애노테이션을 적용하면 됨

(5) 문제

  • 타입과 객체를 통해서 숫자에 문자가 들어오는 것 같은 기본적인 타입 문제는 해결되었음
  • 그러나 타입은 맞지만 숫자의 범위가 기대하는 것과 다른 경우에도 이를 검증 할 수 없음
  • 예를 들어 max-connection의 값을 0으로 설정하게 되면 커넥션이 하나도 만들어지지 않는 심각한 문제가 발생한다고 가정하면 이를 검증할 수 있는 방법이 없음
  • max-connection의 값을 최소 1이상으로 설정하지 않으면 애플리케이션 로딩 시점에 예외를 발생시켜서 빠르게 문제를 인지할 수 있도록 해야하는데, 이것을 스프링이 지원하는 검증기를 통해 해결할 수 있음

3) 검증

(1) 자바 빈 검증기

  • @ConfigurationProperties를 통해서 숫자가 들어가야 하는 부분에 문자가 입력되는 같이 타입이 맞지 않는 데이터를 입력하는 문제는 예방할 수 있었으나 숫자의 범위나 문자의 길이 등과 같은 부분은 검증이 어려움
  • 개발자가 직접 하나하나 검증 코드를 작성해도 되지만 자바에는 자바 빈 검증기(java bean validation)이라는 표준 검증기가 제공됨
  • @ConfigurationProperties는 자바 객체이기 때문에 스프링이 자바 빈 검증기를 사용할 수 있도록 지원함
  • 자바 빈 검증기를 하려면 build.gradle에 아래의 코드를 추가하면 됨
implementation 'org.springframework.boot:spring-boot-starter-validation' //추가

 

(2) MydataSourcePropertiesV3

  • @Validated 적용
  • @NotEmpty 적용: url, username, password는 항상 값이 있어야 함, 필수 값이 됨
  • @Min(1), @Max(999) 적용: maxConnection의 범위는 최소 1부터 최대 999의 값을 허용
  • @DurationMin(seconds = 1), @DurationMax(seconds = 60) 적용: 최소 1초, 최대 60초를 허용
package hello.datasource;

@Getter
@ConfigurationProperties("my.datasource")
@Validated
public class MyDataSourcePropertiesV3 {
    
    @NotEmpty
    private String url;
    @NotEmpty
    private String username;
    @NotEmpty
    private String password;
    private Etc etc;

    public MyDataSourcePropertiesV3(String url, String username, String password, Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }

    @Getter
    public static class Etc {
        @Min(1)
        @Max(999)
        private int maxConnection;
        @DurationMin(seconds = 1)
        @DurationMax(seconds = 60)
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}

 

**참고

  • 패키지 이름에 jakarta.validation으로 시작하는 것은 자바 표준 검증기에서 지원하는 기능임
  • 패키지 이름에 org.hibernate.validator로 시작하는 것은 자바 표준 검증기에서 아직 표준화가 된 기능은 아니고 하이버네이트 검증기라는 표준 검증기의 구현체에서 직접 제공하는 기능임
  • 그러나 대부분 하이버네이트 검증기를 사용하므로 이부분이 크게 문제가 되지는 않을 것임

(3) MyDataSourceConfigV3

  • V3로 변경되는 부분을 제외하고는 기존과 다르지 않음
package hello.config;

@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV3.class)
public class MyDataSourceConfigV3 {

    private final MyDataSourcePropertiesV3 properties;

    public MyDataSourceConfigV3(MyDataSourcePropertiesV3 properties) {
        this.properties = properties;
    }

    // ... 기존 코드 동일
}

 

(4) ExternalReadApplication - 수정 및 실행

  • 기존 @Import 주석처리 및 @Import(MyDataSourceConfigV3.class) 적용 후 설정 데이터의 값들을 검증 범위에 넘어서게 설정해보면 검증기가 동작하면서 오류를 발생하는 로그를 확인할 수 있음
  • 당연히 외부 설정 값들을 검증 범위안으로 설정하면 정상적으로 외부 설정값들이 조회되는 것을 확인할 수 있음

(5) 정리 

  • ConfigurationProperties 덕분에 타입도 안전하고 매우 편리하게 외부 설정을 사용할 수 있으며 검증기 덕분에 쉽고 편리하게 설정 정보를 검증할 수 있음
  • 가장 좋은 예외는 컴파일 예외, 그다음으로 애플리케이션 로딩 시점에 발생하는 예외이며 가장 나쁜 예외는 고객 서비스 중에 발생하는 런타임 예외인데, ConfigurationProperties 덕분에 컴파일과 애플리케이션 로딩 시점에 오류를 빠르게 파악하여 수정할 수 있게 됨

(6) ConfigurationProperties 장점 정리

  • 외부 설정을 객체로 편리하게 변환하여 사용할 수 있음
  • 외부 설정의 계층을 객체로 편리하게 표현할 수 있음
  • 외부 설정을 타입 안전하게 사용할 수 있음
  • 검증기를 적용할 수 있음

7. YAML

1) 설정 데이터 형식

(1) YAML이란?

  • 스프링은 설정 데이터를 사용할 때 application.properties뿐만 아니라 application.yml이라는 형식도 사용함
  • YAML(YAML Ain't Markup Language)은 사람이 읽기 좋은 데이터 구조를 목표로 하며 확장자는 yaml, yml이며 주로 yml을 사용함

(2) application.yml 예시

  • YAML의 가장 큰 특징은 사람이 읽기 좋게 계층 구조를 이룬다는 점임
  • YAML은 space(공백)로 계층 구조를 만드는데, space는 1칸을 사용해도 되고 2칸을 사용해도 되지만 보통 2칸을 사용함
  • 공백을 일관성있게 사용하지 않으면 읽기 어렵거나 구조가 깨질 수도 있기 때문에 2칸을 사용하는것을 권장함
  • 구분 기호로 :(콜론)을 사용하며 값이 있으면 key: value 처럼 :(콜론) 이후에 공백을 하나 입력한 후 값을 넣어주면 됨
  •  스프링은 YAML의 계층 구조를 properties처럼 평평하게 만들어서 읽어들임
environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

2) 적용

(1) application.properties -> application.yml

  • 기존에 작성해둔 application.properties를 사용하지 않도록 파일 이름을 변경 후 application.yml을 작성
  • 계층 구조로 되어있어 구조를 한눈에 파악하기가 쉬움
  • 적용 후 애플리케이션을 실행해보면 정상적으로 입력한 설정 데이터가 조회되는 것을 확인할 수 있음
  • 참고로 실무에서는 설정 정보가 상당히 많아서 보기 편한 yml을 선호함
my:
  datasource:
    url: local.db.com
    username: local_user
    password: local_pw
    etc:
      max-connection: 1
      timeout: 60s
      options: LOCAL, CACHE

 

** 주의!

  • application.properties와 application.yml을 같이 사용하면 application.properties가 우선권을 가짐
  • 그러나 두가지를 함께 사용하는 것은 일관성이 없으므로 권장하지 않음

(2) YML과 프로필

  • YML에도 프로필을 적용할 수 있음
  • --- (dash) 3개를 입력하여 논리 파일을 구분
  • spring.config.active.on-profile을 사용해서 프로필을 적용할 수 있으며 나머지는 application.properties와 동일함
my:
  datasource:
    url: local.db.com
    username: local_user
    password: local_pw
    etc:
      max-connection: 1
      timeout: 60s
      options: LOCAL, CACHE
---
spring:
  config:
    activate:
      on-profile: dev
my:
  datasource:
    url: dev.db.com
    username: dev_user
    password: dev_pw
    etc:
      max-connection: 10
      timeout: 50s
      options: DEV, CACHE
---
spring:
  config:
    activate:
      on-profile: prod
my:
  datasource:
    url: prod.db.com
    username: prod_user
    password: prod_pw
    etc:
      max-connection: 50
      timeout: 10s
      options: PROD, CACHE

8. @Profile

1) 스프링 빈 분리

(1) 예제 상황

  • 프로필과 외부 설정을 사용하여 각 환경마다 설정값을 다르게 적용할 수 있었으나 설정값이 다른 정도가 아니라 각 환경마다 서로 다른 빈을 등록해야 한다면 다른 방법이 필요함
  • 예를 들어 결제 기능을 붙여야 하는데 로컬 개발 환경에서는 실제 결제가 발생하면 문제가 되므로 가짜 결제 기능이 있는 스프링 빈을 등록하고 운영 환경에서는 실제 결제 기능을 제공하는 스프링 빈을 등록한다고 가정

(2) PayClient

  • pay 패키지를 만들어서 작성
  • DI를 적극 활용하기 위하여 인터페이스를 사용
package hello.pay;

public interface PayClient {
    void pay(int money);
}

 

(3) LocalPayClient

  • 로컬 개발 환경에서는 실제 결제를 하지 않는다고 가정
package hello.pay;

@Slf4j
public class LocalPayClient implements PayClient {

    @Override
    public void pay(int money) {
        log.info("로컬 결제 money={}", money);
    }
}

 

(4) ProdPayClient

  • 운영 환경에서는 실제 결제가 시도가 된다고 가정
package hello.pay;

@Slf4j
public class ProdPayClient implements PayClient {

    @Override
    public void pay(int money) {
        log.info("운영 결제 money={}", money);
    }
}

 

(5) OrderService

  • 주문 로직이 있는 Service, PayClient를 사용하는 부분으로 상황에 따라서 LocalPayClient 또는 ProdPayClient를 주입받음
package hello.pay;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final PayClient payClient;

    public void order(int money) {
        payClient.pay(money);
    }
}

 

(6) PayConfig - @Profile 사용

  • @Bean으로 등록하는 설정파일
  • @Profile애노테이션을 사용하면 해당 프로필이 활성화된 경우에만 빈을 등록함
  • localPayClient(): @Profile("default")를 적용하여 default 프로필(기본값)이 활성화 되어 있으면 LocalPayClient를 빈으로 등록
  • prodPayClient(): @Profile("prod")를 적용하여 prod 프로필이 활성화 되어 있으면 ProdPayClient를 빈으로 등록
package hello.pay;

@Slf4j
@Configuration
public class PayConfig {

    @Bean
    @Profile("default")
    public LocalPayClient localPayClient() {
        log.info("LocalPayClient 빈 등록");
        return new LocalPayClient();
    }

    @Bean
    @Profile("prod")
    public ProdPayClient prodPayClient() {
        log.info("ProdPayClient 빈 등록");
        return new ProdPayClient();
    }
}

 

(7) RunOrder

  • ApplicationRunner 인터페이스를 사용하면 스프링은 빈 초기화가 모두 끝나고 애플리케이션 로딩(애플리케이션 컨텍스트 초기화)이 완료되는 시점에 run(args) 메서드를 호출해줌
  • 스프링 부트에서 제공하는 인터페이스로 애플리케이션이 모든 Bean을 초기화한 뒤 실행되어 main메서드 처럼 애플리케이션을 실행할 때 사용하기 유용함
  • ApplicationArguments를 매개변수로 받기 때문에 커맨드 라인 옵션 인수도 활용할 수 있음
package hello.pay;

@Component
@RequiredArgsConstructor
public class RunOrder implements ApplicationRunner {

    private final OrderService orderService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        orderService.order(1000);
    }
}

 

(8) ExternalReadApplication 변경 및 실행

  • hello.pay패키지를 컴포넌트스캔 대상으로 포함하기 위해 @SpringBootApplication 애노테이션에 적용 후 실행
  • 프로필 없이 실행하면 default 프로필이 사용되며 LocalPayClient가 빈으로 등록되어 로컬 결제 money=1000이라는 로그가 출력됨
  • Prod 프로필을 적용 후 실행하면 prod 프로필이 사용되고 ProdPayClient가 빈으로 등록되며 운영 결제 money=1000이라는 로그가 출력 됨
  • 실무에서는 이런 방식을 상당히 많이 사용함
package hello;

@Import(MyDataSourceConfigV3.class)
@SpringBootApplication(scanBasePackages = {"hello.datasource", "hello.pay"})
public class ExternalReadApplication {
    // ... main메서드 동일
}

 

(9) Profile의 정체

  • @Profile은 특정 조건에 따라서 해당 빈을 등록할지 말지 선택하는데, 내부로 들어가보면 @Conditional(ProfileCondition.class)가 적용되어있는 것을 확인할 수 있음
  • 스프링은 @Conditional 기능을 활용해서 개발자가 더 편리하게 사용할 수 있는 @Profile 기능을 제공하는 것임
  • @Profile 을 사용하면 각 환경 별로 외부 설정 값을 분리하는 것을 넘어서, 등록되는 스프링 빈도 분리할 수 있음
// ... 기타 애노테이션 생략
@Conditional({ProfileCondition.class})
public @interface Profile {
    String[] value();
}

 

'인프런 - 스프링 완전정복 코스 로드맵 > 스프링 부트 - 핵심 원리와 활용' 카테고리의 다른 글

마이크로미터/프로메테우스/그라파나, 마이크로미터 소개, 메트릭 확인하기, 다양한 메트릭, 프로메테우스와 그라파나 소개, 프로메테우스(설치/애플리케이션 설정/ 수집 설정/ 기본 기능/게이지와 카운터), 그라파나(설치 및 연동/대시보드 만들기/공유 대시보드 활용/메트릭을 통한 문제 확인)  (2) 2024.12.08
액츄에이터, 프로덕션 준비 기능이란?, 프로젝트 설정 및 액츄에이터 시작, 엔드 포인트 설정, 다양한 엔드포인트, 헬스 정보, 애플리케이션 정보, 로거, HTTP 요청 응답 기록, 액츄에이터와 보안  (2) 2024.12.06
외부설정과 프로필1, 프로젝트 설정 및 외부 설정이란?, 외부 설정(OS 환경 변수/자바 시스템 속성/커맨드 라인 인수/커맨드 라인 옵션 인수/커맨드 라인 옵션 인수와 스프링 부트/스프링 통합), 설정 데이터(외부 파일/내부 파일 분리/내부 파일 합체), 우선순위(설정 데이터/전체)  (1) 2024.12.05
자동 구성(Auto Configuration), 순수 라이브러리 만들기, 순수 라이브러리 사용하기, 자동 구성 라이브러리 만들기, 자동 구성 라이브러리 사용하기, 자동 구성 이해(스프링 부트의 동작/ImportSelector)  (1) 2024.12.02
자동 구성(Auto Configuration), 프로젝트 설정 및 예제 만들기, 자동 구성 확인, 스프링 부트의 자동 구성, 자동 구성 직접만들기 - 기반 예제, @Conditional, @Conditional - 다양한 기능  (1) 2024.12.02