FireDrago

[SpringBoot] 다양한 외부 설정 조회방법 본문

프로그래밍/SpringBoot

[SpringBoot] 다양한 외부 설정 조회방법

화이용 2024. 5. 31. 12:04

Environment

 

스프링이 제공하는 추상화된 인터페이스이다. 

Environment는 Os 환경변수, 자바 환경변수, 어플리케이션 파라미터 , 외부 설정 파일을 가리지 않고

설정값을 가져올 수 있게 해준다.

my:
  datasource:
    url: local.db.com
    username: local_user
    password: local_pw
    etc:
      max-connection: 1
      timeout: 3500ms
      options: CACHE, ADMIN
---
spring:
  config:
    activate:
      on-profile: dev
my:
  datasource:
    url: dev.db.com
    username: dev_user
    password: dev_pw
    etc:
      maxConnection: 10
      timeout: 60s
      options: DEV, CACHE
---
spring:
  config:
    activate:
      on-profile: prod
my:
  datasource:
    url: prod.db.com
    username: prod_user
    password: prod_pw
    etc:
      maxConnection: 50
      timeout: 10s
      options: PROD, CACHE

application.yml 파일의 설정이다. 프로필 별로 설정값이 다르게 설정되어 있다.

@Slf4j
@Data
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);
    }
}

먼저 외부 설정을 저장할 MyDataSource 클래스를 만들었다.

@PostConstruct 어노테이션을 사용하여 객체가 생성된 이후 필드값을 로그출력 하도록 했다.

 

@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");
        Integer maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
        Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);
        List options = env.getProperty("my.datasource.etc.options", List.class);

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

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

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

}

@Configuration 클래스 에서 빈을 생성했다. 

Environment 인터페이스를 주입받고,

Environment.getProperty(key, Type) 메서드에 key 값반환타입을 파라미터로 전달한다.

반환타입을 주면 해당 타입으로 변환해준다.

 

이 방식의 단점은 Environment 를 직접 주입받고, env.getProperty(key) 를 통해서 값을 꺼내는 과정을 반복해야 한다

 

@Value 

@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:1}") 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);
    }
}


@Import(MyDataSourceValueConfig.class)
@SpringBootApplication(scanBasePackages = {"hello.datasource", "hello.pay"})
public class ExternalReadApplication {

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

}
@Value("${my.datasource.etc.max-connection:1}") int maxConnection

기본값을 사용하려면 : 뒤에 기본값을 적어준다.

 

@ConfigurationProperties

스프링은 외부 설정의 묶음정보를 객체로 변환하는 기능을 제공한다. 객체를 사용하면, 잘못된 타입이 들어오는 경우를

방지 할 수 있고 활용성이 더 증가한다. 이를 타입 안전한 설정 속성이라고 한다. 타입이 다르면 오류가 발생하는 것이다

 

@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<>();
    }
}

@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;

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

@ConfigurationProperties("my.datasource") 클래스에 어노테이션을 붙이면, 외부설정으로 객체를 만들 수 있다.

필드명은 외부 설정의 키 값에 맞춘다. 기본 주입 방식은 자바빈 프로퍼티 방식이다. Getter , Setter 가 필요하다. 

하지만 setter는 부작용이 크므로, 생성자방식도 지원한다. 

MyDataSourcePropertiesV1 : 자바빈 프로퍼티 방식

MyDataSourcePropertiesV2 : 생성자 방식

 

@DefaultValue : 해당 값을 찾을 수 없는 경우 기본값을 사용한다.
@DefaultValue Etc etc : etc 를 찾을 수 없을 경우 Etc 객체를 생성하고 내부에 들어가는 값은 비워둔다. ( null , 0 )
@DefaultValue("DEFAULT") List<String> options : options 를 찾을 수 없을 경우 DEFAULT 라는 이름의 값을 사용한다.

 

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

    private final MyDataSourcePropertiesV2 properties;

    public MyDataSourceConfigV2(MyDataSourcePropertiesV2 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());
    }
}

@EnableConfigurationProperties(MyDataSourcePropertiesV2.class)

    스프링에게 사용할 @ConfigurationProperties 를 지정해준다. 이렇게 하면 스프링 빈으로 등록되고 주입받을 수 있다.

    생성자 주입을 통해 주입받은 것을 볼 수 있다.

 

여러개의 @ConfigurationProperties 를 등록할때는 @ConfigurationPropertiesScan 을 사용한다. 범위를 지정할 수 있다.

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {}

 

<@ConfigurationProperties 검증>

@ConfigurationProperties 를 사용할때, 자바 빈 검증기를 사용할 수 있다.

build.gradle 에 validation을 추가해준다.

implementation 'org.springframework.boot:spring-boot-starter-validation'
@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;
        }
    }
}

@NotEmpty : url , username , password 는 항상 값이 있어야 한다. 필수 값이 된다.

@Min(1) @Max(999) maxConnection : 최소 1 , 최대 999 의 값을 허용한다.
@DurationMin(seconds = 1) @DurationMax(seconds = 60) : 최소 1, 최대 60초를 허용한다.