Spring/[인프런 김영한 스프링 부트 - 핵심 원리와 활용]

[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 외부설정과 프로필2

h2boom 2025. 1. 23. 16:43

외부설정과 프로필

외부 설정 사용 - Environment

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

 

@Slf4j
public class MyDataSource {
    private String url;
    private String username;
    private String password;
    private int maxConnection;
    private Duration timeout;
    private List<String> options;

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

    @PostConstruct
    public void init() {
        log.info("url={}", url);
        log.info("username={}", username);
        log.info("password={}", password);
        log.info("maxConnection={}", maxConnection);
        log.info("timeout={}", timeout);
        log.info("options={}", options);
    }
}
  • DataSource 예제
    • maxConnection : 최대 연결 수
    • timeout : 응답 지연시 타임아웃
    • options : 연결 시 사용하는 기타 옵션들
    • @PostConstruct를 통해 설정된 값을 출력

 

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
  • 외부 속성 설정 예시
    • 외부 속성은 application.properties 설정 데이터를 사용
    • 별도의 프로필을 사용하지 않았다.
    • properties는 소문자와 -(dash)를 사용하는 캐밥 표기법을 사용한다.
      • ex) max-connection

 

@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);
    }
}
  • 자바 설정 파일 예제
    • MyDataSource를 스프링 빈으로 등록하는 자바 설정 파일이다.
    • Environment를 사용하여 외부 설정의 종류와 관계없이 코드 안에서 일관성있게 외부 설정을 조회할 수 있다.
    • Environment.getProperty(key, type) : 호출 시 타입 정보를 주면 스프링 내부 변환기에 의해 문자를 해당 타입으로 변환해준다.

 

@Import(MyDataSourceEnvConfig.class)
@SpringBootApplication(scanBasePackages = "hello.datasource")
public class ExternalReadApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExternalReadApplication.class, args);
    }

}
  • 스프링 애플리케이션 실행 파일 예제
    • 설정 정보를 빈으로 등록하기 위해 @Import 사용
    • @SpringBootApplication(scanBasePackages=~)
      • 예제에서 설정 정보를 계속 추가할 것이기 때문에 컴포넌트 스캔 대상 패키지를 config 패키지를 제외하고 설정 파일은 별도로 @Import를 통해 빈으로 등록

 

  • Environment 방식
    • 장점 : Environment를 사용하여 외부 설정의 종류와 관계없이 코드 안에서 일관성있게 외부 설정을 조회할 수 있다.
    • 단점 : Environment를 직접 주입받고 env.getProperty()를 통해 값을 거내는 과정을 반복해야 한다.

외부설정 사용 - @Value

  • @Value도 내부에서 Environment를 사용한다.

 

@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 maxConnection;
    @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, maxConnection, 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 maxConnection,
                                      @Value("${my.datasource.etc.timeout}") Duration timeout,
                                      @Value("${my.datasource.etc.options}") List<String> options) {
        return new MyDataSource(url, username, password, maxConnection, timeout, options);
    }
}
  • @Value를 사용한 자바 설정 파일 예제
    • @Value에 ${}를 사용해 외부 설정의 키 값을 주면 원하는 값을 주입받을 수 있다.
    • @Value는 필드에 사용할 수도 있고 파라미터에 사용할 수도 있다.
      • myDataSource1()은 필드에 주입 받은 설정 값을 사용
      • myDataSource2()는 파라미터를 통해서 설정 값을 주입
    • @Value에서 키를 찾지 못하는 경우 기본 값을 사용하려면 : 뒤에 기본값을 적어주면 된다.
      • ex) @Value("${mydatasource.etc.max-connection:1}")

 

  • @Value 방식
    • 장점 : application.properties 설정 데이터에 외부 설정을 추가하고 @Value로 해당 값들을 읽어올 수 있다.
    • 단점 : 하나하나 외부 설정 정보의 키 값을 입력 받고 주입 받아야하는 번거로움이 존재

외부설정 사용 - @ConfigurationProperties

  • Type-safe Configuration Properties(타입 안전한 설정 속성) : 스프링에서 제공하는 외부 설정의 묶음 정보를 객체로 변환하는 기능이다.
    • 잘못된 타입이 들어오는 문제를 방지할 수 있다.
    • 객체를 통한 활용할 수 있는 부분들이 많아진다.

 

@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<>();
    }
}
  • @ConfigurationProperties 등록 예제
    • 외부 설정을 주입 받을 객체를 생성하고 각 필드를 외부 설정의 키 값에 맞춰 준비
    • @ConfigurationProperties가 있으면 외부 설정을 주입 받을 객체라는 의미
      • 외부 설정 key 묶음 시작점인 my.datasource를 적어준다.
        application.properties 설정에서 my.datasource.~로 시작하기 때문
      • my.datasource.etc.~인 속성들은 별도의 내부 클래스를 통해 다룰 수 있다.
    • 기본 주입 방식은 자바빈 프로퍼티 방식으로 Getter, Setter가 필요하다.

 

@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());
    }
}
  • @ConfigurationProperties 사용 예제
    • @EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
      • 스프링에게 사용할 @ConfigurationProperties를 지정해줌으로 해당 클래스는 스프링 빈으로 등록되고 필요한 곳에서 주입받아서 사용할 수 있다.
  • 스프링에서 properties에서 사용한 캐밥 표기법을 자바의 낙타 표기법으로 변환해준다.

 

  • @ConfigurationProperties를 하나하나 직접 등록할 때는 @EnableConfigurationProperties를 사용
  • @ConfigurationProperties를 특정 범위로 자동 등록할 때는 @ConfigurationPropertiesScan을 사용
    • 빈을 직접 등록하는 것과 컴포넌트 스캔을 사용하는 차이와 유사하다.

 

  • V1 예제 문제점
    • Setter가 있기 때문에 실수로 값을 변경하는 문제가 발생할 수 있다.
      • Setter를 제거하고 생성자를 사용하면 중간에 데이터를 변경하는 실수를 방지할 수 있다.

외부설정 사용 - @ConfigurationProperties 생성자

  • @ConfigurationProperties는 V1 예제와 같이 Getter, Setter를 사용하는 자바빈 프로퍼티 방식뿐만 아니라 생성자를 통해 객체를 만드는 기능도 지원한다.

 

@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, @DefaultValue 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 = new ArrayList<>();

        public Etc(int maxConnection, Duration timeout, @DefaultValue("DEFAULT") List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}
  • 생성자를 사용한 @ConfigurationProperties 등록 예제
    • @DefaultValue : 해당 값을 찾을 수 없는 경우 기본 값을 사용한다.
      • @DefaultValue Etc etc : etc 객체를 찾을 수 없는 경우 Etc 객체를 생성하고 내부에 들어가는 값은 비워둔다.
      • @DefaultValue("DEFAULT") List<String> options : options를 찾을 수 없는 경우 DEFAULT라는 이름의 값을 사용한다.
  • @ConstructorBinding : 스프링 3.0부터는 생성자가 하나일 경우 생략할 수 있으며 생성자가 둘 이상인 경우 사용할 생성자에 @ConstructorBinding 어노테이션을 적용해줘야한다.

 

  • V2 예제 문제점
    • 타입은 일치하지만 숫자 범위가 기대하는 것과 다른 경우 문제가 발생
      • ex) max-connection의 값을 0으로 설정하면 커넥션이 만들어지지 않는 문제가 발생

외부설정 사용 - @ConfigurationProperties 검증

  • @ConfigurationProperties 사용 시 타입이 일치하지 않는 문제에 대해서는 예방할 수 있다.
    • 하지만 숫자의 범위나 문자의 길이같은 부분은 검증하기 어렵다.

 

  • @ConfigurationProperties는 자바 객체이기 때문에 자바 빈 검증기(java bean validation)를 사용할 수 있다.
    • 자바 빈 검증기를 통해 숫자 범위, 문자 길이 등을 검증할 수 있다.

 

implementation 'org.springframework.boot:spring-boot-starter-validation' //추가
  • 자바 빈 검증기를 사용하기 위해 build.gradle에 의존성 추가

 

@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, @DefaultValue("DEFAULT") List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}
  • 자바 빈 검증기 적용 예제
    • 자바 빈 검증기 사용을 위해 @Validated 어노테이션을 적용
    • @NotEmpty : 해당 필드에 항상 값이 있어야 한다.
    • @Min, @Max : 해당 필드의 최소, 최대값을 지정
    • @DurationMin(), @DurationMax() : 해당 필드의 최소, 최대 시간을 지정

 

  • ConfigurationProperties 장점
    • 외부 설정을 객체로 편리하게 변환해서 사용할 수 있다.
    • 외부 설정의 계층을 객체로 편리하게 표현할 수 있다.
    • 외부 설정을 타입 안전하게 사용할 수 있다.
    • 검증기를 적용할 수 있다.

YAML

  • 스프링은 설정 데이터 사용 시 .properties, .yml 형식을 지원한다.

 

  • YAML : YAML Ain't Markup Language로 사람이 읽기 좋은 데이터 구조를 목표로 한다.
    • 확장자는 yaml, yml로 주로 yml을 사용

 

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App
  • .properties 예시

 

environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"
  • .yml 예시
    • YAML의 가장 큰 특징은 사람이 읽기 좋게 계층 구조를 이룬다.
    • YAML은 공백으로 계층 구조를 이루며 공백 1칸을 사용해도 되지만 주로 2칸을 사용하며 일관성 있게 사용해야 한다.
    • 구분 기호로 :를 사용하며 : 이후 공백 1칸을 넣고 값을 넣어주면 된다.

 

  • properties, yml을 함께 사용 시 properties가 우선권을 가진다.
    • 둘을 함께 사용하는 것은 일관성이 없기 때문에 권장되지 않으며 실무에서는 보기 편한 yml을 선호

 

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: 60s
      options: LOCAL, 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
  • application.yaml 파일 프로필 설정 예제
    • yml은 ---(dash) 3개를 사용해서 논리 파일을 구분
    • spring.config.active.on-profile을 사용해서 프로필을 적용할 수 있다.

 


@Profile

  • 프로필과 외부 설정을 사용해서 각 환경마다 설정 값을 다르게 설정할 수 있다.
  • 설정 값외에 각 환경마다 서로 다른 빈을 등록해야 하는 경우 @Profile을 사용하면 된다.
    • ex) 로컬 개발 환경에서는 실제 결제가 발생하지 않도록 가짜 결제 기능의 스프링 빈을 등록

 

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

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

public class LocalPayClient implements PayClient {
    @Override
    public void pay(int money) {
        log.info("로컬 결제 money={}", money);
    }
}
  • 로컬 환경과 운영 환경에서 적용되는 인터페이스 구현체 생성
    • 로컬 환경에서는 LocalPayClient 빈만 등록되도록 하고 운영 환경에서는 ProdPayClient 빈만 등록되도록 할 것 

 

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

    @Bean
    @Profile("prod")
    public ProdPayClient payClient() {
        log.info("ProdPayClient 빈 등록");
        return new ProdPayClient();
    }
}
  • 자바 설정 파일 예제
    • @Profile : 해당 프로필이 활성화된 경우에만 빈을 등록한다.
      • default 프로필(기본 값)이 활성화되면 LocalPayClient를 빈으로 등록
      • prod 프로필이 활성화되면 ProdPayClient를 빈으로 등록

 

@Component
@RequiredArgsConstructor
public class RunOrder implements ApplicationRunner {
    private final OrderService orderService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        orderService.order(1000);
    }
}
  • ApplicationRunner 인터페이스 구현
    • ApplicationRunner 인터페이스를 사용하면 스프링은 빈 초기화가 모두 끝나고 애플리케이션 로딩이 완료되는 시점에 run(args) 메서드를 호출해준다.
    • 주로 별도의 WAS가 없는 경우 로직 호출을 위해 사용한다.

정리

  • 스프링이 지원하는 다양한 외부 설정 조회 방법
    1. Environment
      • Environment를 직접 주입받고 env.getProperty()를 통해 값을 거내는 과정을 반복해야 하기에 번거롭다.
    2. @Value - 값 주입
      • 하나하나 외부 설정 정보의 키 값을 입력 받고 주입 받아야하는 번거롭다.
    3. @ConfigurationProperties - 타입 안전한 설정 속성
      • 외부 설정의 묶음 정보를 객체로 변환하는 기능으로 잘못된 타입이 들어오는 문제를 방지할 수 있고 객체를 통한 활용할 수 있는 부분들이 많아진다.
      • @ConfigurationProperties를 하나하나 직접 등록할 때는 @EnableConfigurationProperties를 사용
      • @ConfigurationProperties를 특정 범위로 자동 등록할 때는 @ConfigurationPropertiesScan을 사용
      • 자바 빈 검증기(@Validated)를 사용할 수 있다.

 

  • YAML
    • YAML의 가장 큰 특징은 사람이 읽기 좋게 계층 구조를 이룬다.
    • YAML은 공백으로 계층 구조를 이루며 공백 1칸을 사용해도 되지만 주로 2칸을 사용하며 일관성 있게 사용해야 한다.
    • 구분 기호로 :를 사용하며 : 이후 공백 1칸을 넣고 값을 넣어주면 된다.
  • properties, yml을 함께 사용 시 properties가 우선권을 가지며 둘을 함께 사용하는 것은 일관성이 없기 때문에 권장되지 않으며 실무에서는 보기 편한 yml을 선호

 

  • @Profile : 해당 프로필이 활성화된 경우에만 빈을 등록한다.
    • 설정 값외에 각 환경마다 서로 다른 빈을 등록해야 하는 경우 @Profile을 사용하면 된다.

출처 : [인프런 김영한 스프링 부트 - 핵심 원리와 활용]

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard

 

스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕! 실무에 필요한 내용을 모두 담았습니다.  [임베딩 영상] 김영한의 스

www.inflearn.com