![[Spring Boot 핵심 원리와 활용] 외부 설정](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpyMD5%2FbtsNJH6QG2Z%2FCwf46n6h6XtCLmF13hXkl0%2Fimg.png)
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
외부 설정이란 애플리케이션을 실행할 때 필요한 설정값을 외부에서 불러와서 전달하는 것을 의미한다.
외부 설정은 일반적으로 다음 4가지 방법이 있다.
- OS 환경 변수: OS에서 지원하는 외부 설정, 해당 OS를 사용하는 모든 프로세스에서 사용
- 자바 시스템 속성: 자바에서 지원하는 외부 설정, 해당 JVM안에서 사용
- 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정, 실행시 main(args) 메서드에서 사용
- 외부 파일(설정 데이터): 프로그램에서 외부 파일을 직접 읽어서 사용
스프링 통합
- 커맨드 라인 옵션 인수, 자바 시스템 속성, OS 환경변수는 모두 외부 설정을 key=value 형식으로 사용할 수 있는 방법이다.
- 이 외부 설정값을 읽어서 사용하는 개발자 입장에서 단순하게 생각해보면, 모두 key=value 형식이고, 설정값을 외부로 뽑아둔 것이다.
- 그런데 어디에 있는 외부 설정값을 읽어야 하는지에 따라서 각각 읽는 방법이 다르다는 단점이 있다.
- 외부 설정값이 어디에 위치하든 상관없이 일관성 있고, 편리하게 key=value 형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.
- 스프링은 이 문제를 Environment 와 PropertySource 라는 추상화를 통해서 해결한다.
PropertySource
- org.springframework.core.env.PropertySource
- 스프링은 PropertySource 라는 추상 클래스를 제공하고, 각각의 외부 설정을 조회하는 XxxPropertySource 구현체를 만들어두었다.
- 스프링은 로딩 시점에 필요한 PropertySource 들을 생성하고, Environment 에서 사용할 수 있게 연결해둔다.
Environment
- org.springframework.core.env.Environment
- Environment 를 통해서 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있다.
- environment.getProperty(key) 를 통해서 값을 조회할 수 있다.
- Environment 는 내부에서 여러 과정을 거쳐서 PropertySource 들에 접근한다.
- 모든 외부 설정은 이제 Environment 를 통해서 조회하면 된다.
설정 데이터(파일)
- application.properties , application.yml도 PropertySource 에 추가된다.
- 따라서 Environment 를 통해서 접근할 수 있다.
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class EnvironmentCheck {
private final Environment env;
public EnvironmentCheck(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
String url = env.getProperty("url");
String username = env.getProperty("username");
String password = env.getProperty("password");
log.info("env url={}", url);
log.info("env username={}", username);
log.info("env password={}", password);
}
}
외부 설정
외부의 application.yml
- 개발자는 application.properties 라는 이름의 파일을 자바를 실행하는 위치에 만들어 두기만 하면 된다.
- 그러면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource 의 구현체를 제공한다.
- 스프링에서는 이러한 application.properties 파일을 설정 데이터(Config data)라 한다.
- 당연히 설정 데이터도 Environment 를 통해서 조회할 수 있다.
내부의 application.yml
프로필을 분리
설정 파일을 외부에 관리하는 것은 상당히 번거로운 일이다.
설정을 변경할 때 마다 서버에 들어가서 각각의 변경 사항을 수정해두어야 한다.
이 문제를 해결하는 간단한 방법은 설정 파일을 프로젝트 내부에 포함해서 관리하는 것이다. 그리고 빌드 시점에 함께 빌드되게 하는 것이다.
이렇게 하면 애플리케이션을 배포할 때 설정 파일의 변경 사항도 함께 배포할 수 있다.
프로필
- 스프링은 이런 곳에서 사용하기 위해 프로필이라는 개념을 지원한다.
- spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다.
- 그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 파일(설정 데이터)을 조회한다.
application-{profile}.properties
- spring.profiles.active=dev -> dev 프로필이 활성화 ->application-dev.properties
- spring.profiles.active=prod -> prod 프로필이 활성화 -> application-prod.properties 를 설정 데이터로 사용
application-dev.yml -> 개발 프로필에서 사용
url: dev.db.com username: dev_user password: dev_pw |
application-prod.yml -> 운영 프로필에서 사용
url: prod.db.com username: prod_user password: prod_pw |
jar을 실행할 때 dev 프로필을 지정하는 방법은 아래와 같다.
- java -jar app.jar --spring.profiles.active=dev
프로필을 통합
url: local.db.com username: local_user password: local_pw --- spring: config: activate: on-profile: dev url: dev.db.com username: dev_user password: dev_pw --- spring: config: activate: on-profile: prod url: prod.db.com username: prod_user password: prod_pw |
- 속성 파일 구분 기호에는 선행 공백이 없어야 하며 정확히 3개의 하이픈 문자가 있어야 한다.
- 구분 기호 바로 앞과 뒤의 줄은 같은 주석 접두사가 아니어야 한다.
스프링은 문서를 위에서 아래로 순서대로 읽으면서 설정한다.
처음에 나오는 설정은 spring.config.activate.on-profile 와 같은 프로필 정보가 없다.
따라서 프로필이 설정되어있지 않다면 아래와 같은 설정 정보가 적용된다.
url: local.db.com username: local_user password: local_pw |
우선 순위
- jar 내부 application.yml
- jar 내부 프로필 분리 (application-{profile}.yml)
- jar 외부 application.yml
- jar 외부 프로필 분리 (application-{profile}.yml)
외부 설정 사용
@Value
- @Value 를 사용하면 외부 설정값을 편리하게 주입받을 수 있다.
- @Value도 내부에서는 Environment 를 사용한다.
application.yml
my: db: url: jdbc:mysql://localhost:3306/mydb username: user password: pass |
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyDatabaseConfig {
@Value("${my.db.url}")
private String url;
@Value("${my.db.username}")
private String username;
@Value("${my.db.password}")
private String password;
public void printInfo() {
System.out.println("URL = " + url);
System.out.println("Username = " + username);
System.out.println("Password = " + password);
}
}
@ConfigurationProperties
- @ConfigurationProperties는 외부 설정값을 하나의 클래스로 구조화해 주입할 수 있어, 관련 설정을 그룹 단위로 관리할 수 있다는 점에서 @Value보다 유지보수성이 높다.
- 특히 설정 항목이 많아질수록 매번 @Value를 사용하는 방식은 반복과 분산이 심해지는데, @ConfigurationProperties는 계층 구조 그대로 매핑하여 한 번에 주입할 수 있고, 이를 통해 설정을 명확히 분리하고 도메인화할 수 있다.
- 또한 타입 안정성이 뛰어나고, IDE 자동완성과 유효성 검사를 적용할 수 있다는 점에서도 장점이 크다.
Type-safe Configuration Properties
스프링은 외부 설정의 묶음 정보를 객체로 변환하는 기능을 제공한다.
이것을 타입 안전한 설정 속성이라 한다. 객체를 사용하면 타입을 사용할 수 있다.
따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다.
쉽게 이야기해서 외부 설정을 자바 코드로 관리할 수 있는 것이다. 그리고 설정 정보 그 자체도 타입을 가지게 된다.
application.yml
my: datasource: url: local.db.com username: local_user password: local_pw etc: max-connection: 1 timeout: 3500ms options: CACHE,ADMIN |
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import java.time.Duration;
import java.util.List;
@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("DEFAULT") 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 이 있으면 외부 설정을 주입 받는 객체라는 뜻이다.
여기에 외부 설정 KEY의 묶음 시작점인 my.datasource 를 적어준다. - 생성자를 만들어 두면 생성자를 통해서 설정 정보를 주입한다.
- @Getter 롬복이 자동으로 getter 를 만들어준다.
- @DefaultValue : 해당 값을 찾을 수 없는 경우 기본값을 사용한다.
- @DefaultValue Etc etc : etc 를 찾을 수 없을 경우 Etc 객체를 생성하고 내부에 들어가는 값은 비워둔다. (null , 0)
@ConstructorBinding
스프링 부트 3.0 이전에는 생성자 바인딩 시에 @ConstructorBinding 애노테이션을 필수로 사용해야 했다.
스프링 부트 3.0 부터는 생성자가 하나일 때는 생략할 수 있다.
생성자가 둘 이상인 경우에는 사용할 생성자에@ConstructorBinding 애노테이션을 적용하면 된다.
표기법 변환
스프링은 캐밥 표기법을 자바 낙타 표기법으로 중간에서 자동으로 변환해준다.
application.properties 에서는 max-connection 자바 코드에서는 maxConnection
main 메서드 위에 @ConfigurationPropertiesScan 애노테이션을 추가하면 이제 외부 설정 값을 주입받을 수 있다.
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
검증 기능
@ConfigurationProperties은 자바 객체이기 때문에 스프링이 자바 빈 검증기를 사용할 수 있도록 지원한다.
아래와 같이 설정 파일의 값이 제대로 입력되었는지 검증할 수 있다.
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotEmpty; import lombok.Getter; import org.hibernate.validator.constraints.time.DurationMax; import org.hibernate.validator.constraints.time.DurationMin; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; import java.time.Duration; import java.util.List; @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; } } }
@Profile
설정값이 다른 정도가 아니라 각 환경마다 서로 다른 빈을 등록해야 한다면 @Profile 애노테이션을 사용할 수 있다.
예를 들어서 결제 기능을 붙여야 하는데, 로컬 개발 환경에서는 실제 결제가 발생하면 문제가 되니 가짜 결제 기능이 있는 스프링 빈을 등록하고, 운영 환경에서는 실제 결제 기능을 제공하는 스프링 빈을 등록한다고 가정해보자.
public interface PayClient {
void pay(int money);
}
@Slf4j
public class LocalPayClient implements PayClient {
@Override
public void pay(int money) {
log.info("로컬 결제 money={}", money);
}
}
@Slf4j
public class ProdPayClient implements PayClient {
@Override
public void pay(int money) {
log.info("운영 결제 money={}", money);
}
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final PayClient payClient;
public void order(int money) {
payClient.pay(money);
}
}
@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();
}
}
- @Profile 애노테이션을 사용하면 해당 프로필이 활성화된 경우에만 빈을 등록한다.
- default 프로필(기본값)이 활성화 되어 있으면 LocalPayClient 를 빈으로 등록한다.
- prod 프로필이 활성화 되어 있으면 ProdPayClient 를 빈으로 등록한다.
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot 핵심 원리와 활용] 자동 구성 라이브러리 제작 및 사용 (0) | 2025.05.02 |
---|---|
[스프링 핵심원리 - 고급] 스프링 AOP 실무 주의사항 (0) | 2025.04.27 |
[Spring 핵심원리 - 고급] 포인트컷 지시자(PCD) (0) | 2025.04.27 |
[Spring 핵심원리 - 고급] 스프링 AOP 구현 (0) | 2025.04.21 |
[Spring 핵심원리 - 고급] @Aspect AOP (0) | 2025.04.17 |