일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 스프링 db2 - 데이터 접근 기술
- 스프링 입문(무료)
- 자바의 정석 기초편 ch14
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch13
- @Aspect
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch5
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch9
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch11
- 2024 정보처리기사 시나공 필기
- 스프링 db1 - 스프링과 문제 해결
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch6
- jpa - 객체지향 쿼리 언어
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch12
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch7
- 스프링 mvc1 - 서블릿
- 게시글 목록 api
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch3
- 스프링 고급 - 스프링 aop
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch8
- Today
- Total
나구리의 개발공부기록
외부설정과 프로필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();
}