@Value vs @ConfigurationProperties
0. 들어가기 앞서
Spring 에서의 환경 변수 관리 방법이 @Value 를 사용하는 방법만 알고 있었는데,
공부하다 보니 좀 더 체계적으로 관리할 수 있는 방법이 있다는 것을 알게 되어
비교하고 각각의 장단점을 정리해보고자 한다.
0-1. 기본 환경 변수 설정
application.properties
properties
greeting-name=taewoo
greeting-coffee=${greeting-name} is drinking Cafe Cereza
application.yml
yaml
greeting:
name: taewoo
coffee: ${greeting.name} is drinking Cafe Cereza
1. @Value 를 사용하는 경우
아래와 같이 POJO 형태의 클래스를 먼저 만들었다.
- POJO : 특정 기술이나 프레임워크에 종속되지 않은, 순수한 형태의 기본 자바 객체
Coffee.java
java
@Component // 빈 등록 필수 @Getter public class Coffee { @Value("${greeting-name:someone}") // @Value("${greeting.name:someone}") → yml 파일의 경우 // application.properties 의 greeting-name 변수 확인 → 없으면 someone 으로 설정 private final String name; @Value("${greeting-coffee:${greeting-name} is drinking ice coffee}") // greeting-coffee 변수 확인 → 없으면 : 뒤의 값으로 설정 private final String place; public Coffee() { // 우선순위 확인을 위한 기본 생성자 this("기본생성자", "여기 있어요"); } public Coffee(String name, String place) { this.name = name; this.place = place; } }
테스트 코드
java
@SpringBootTest class CoffeeServiceTest { @Autowired private CoffeeService coffeeService; @Test void getCoffee() { System.out.println("커피 주문자 : " + coffeeService.getCoffee().getName()); System.out.println("카페 장소 : " + coffeeService.getCoffee().getPlace()); } }
이렇게 기본 생성자가 정의되어 있어도 호출하는 방식에 따라 결과가 다르게 나온다. (개발자 입장에서 헷갈릴 수 있음)
1-1. Spring 컨테이너에 의해 빈으로 생성되는 경우
CoffeeService.java
java
@Service @RequiredArgsConstructor public class CoffeeService { private final Coffee coffee; public Coffee getCoffee() { return coffee; } }
💡
동작 과정
Coffee()<기본생성자> 호출@Value가 붙은 필드에 설정파일(properties, yml) 의 값을 주입한다.- 결과적으로 환경변수가 기존 생성자로 초기화된 값을 덮어쓰게 된다.
결과

1-2. 직접 클래스 호출
CoffeeService.java
java
@Service public class CoffeeService { // bean 을 통해 주입되는 것이 아니라 직접 생성 (@Component 가 있으나 마나) private Coffee coffee = new Coffee(); public Coffee getCoffee() { return coffee; } }
⚠️
Spring 컨테이너에서 주입받은 (DI) 객체가 아닌 새로 만든 객체이기 때문에
따라서 일반적인 객체 생성과정과 동일하게 이루어지기 때문에 아래와 같은 결과가 나온다.
@Value 가 동작하지 않는다.따라서 일반적인 객체 생성과정과 동일하게 이루어지기 때문에 아래와 같은 결과가 나온다.
결과

1-3. @Value 방식의 단점
❗
-
테스트의 어려움
단위 테스트를 위해 객체 생성 시 Spring 컨텍스트를 반드시 띄워야 한다.
단순 객체 테스트를 위해 Spring 프레임워크를 의존해야 하여 테스트 속도가 느려진다. -
final 키워드와의 충돌
private final String name;과 같이 final 로 선언하고 싶어도@Value는 Spring 이 객체 생성 후리플렉션(Reflection API)을 통해 억지로 집어넣는 방식이다.
final 은 생성자에서 초기화되어야 하는데 필드 주입은 객체 생성 이후에 발생 → final 사용 불가 또는 런타임 오류 발생 가능 -
순환 참조 및 초기화 시점 문제
@Value주입 시점은Bean생성 완료 이후이다.
생성자 내부 로직에서@Value로 주입받은 변수를 사용하려 하면 주입 전이므로 null 값이 나온다.
하지만 이러한 단점 외에도 간결함 및 빠른 프로토타이핑 (복잡 설정 없이 빠르게 기능 구현하거나, 단순 설정 클래스 만들 때) 생산성이 높다.
1-4. 단점 극복 방안 — @Value 를 이용한 생성자 주입
java
@Component public class Coffee { private final String name; private final String place; // 생성자 주입 public Coffee( @Value("${greeting-name:someone}") String name, @Value("${greeting-coffee:${greeting-name} is drinking ice coffee}") String place ) { this.name = name; this.place = place; } }
✅
효과
- 테스트 용이 :
new Coffee("이름", "장소");로 간단히 단위 테스트 가능 - 불변성 보장 : final 필드를 안전하게 사용 가능
- 순환 참조 방지 : 객체 생성 시점에 모든 의존성이 주입되므로 초기화 오류 없음
- DI 프레임워크 의존성 분리 : Spring 이 아닌 다른 환경에서도 독립적으로 동작 가능
1-5. 생성자 주입의 결과
Value 미동작 일반 객체처럼 사용
java
@Service public class CoffeeService { // 기본생성자 호출 시 컴파일 에러 private final Coffee coffee = new Coffee("이름", "장소"); public Coffee getCoffee() { return coffee; } }

Value 동작 의존성 주입 사용
java
@Service @RequiredArgsConstructor public class CoffeeService { private final Coffee coffee; public Coffee getCoffee() { return coffee; } }

2. @ConfigurationProperties
사용 의도 : 속성을 정의하고 관련 속성을 그룹화해서, 도구로 검증 가능하고 타입 세이프한 방식으로 속성을 참조하고 사용하기 위함
2-1. 사용해보기
Greeting.java
java
@Getter @Setter @ConfigurationProperties(prefix = "greeting") // greeting.name, greeting.coffee 처럼 application.properties 에 설정 가능 public class Greeting { private String name; private String coffee; }
TaepangApplication.java (main)
java
@SpringBootApplication @ConfigurationPropertiesScan // @ConfigurationProperties 를 스캔하기 위해 필요 (또는 Greeting 에 @Component 추가) public class TaepangApplication { public static void main(String[] args) { SpringApplication.run(TaepangApplication.class, args); } }
CoffeeService.java
java
@Service @RequiredArgsConstructor public class CoffeeService { private final Greeting greeting; // 의존성 주입 public Greeting getCoffee() { return greeting; } }
build.gradle
groovy
implementation 'org.springframework.boot:spring-boot-configuration-processor'
application.properties
properties
greeting.name=newName
greeting.coffee=helloCoffee
// 위 의존성 추가 이후 재빌드 하면 위와 같이 기본 변수 설정이 가능하다.
테스트 코드
java
@SpringBootTest class CoffeeServiceTest { @Autowired private Greeting greeting; @Test void getCoffee() { System.out.println("커피 주문자 : " + greeting.getName()); System.out.println("카페 장소 : " + greeting.getCoffee()); } }
테스트 결과

💡
@Value 와의 차이
- 설정(환경 변수)을 하나의 POJO 로 정의할 수 있다.
- IDE 의 자동완성 가능
-
기본값 관리의 명확성 : 코드 내에서 직접 확인 가능
java객체 생성 시점에 이미 초기값이 설정되므로, Spring 이 값을 주입하지 않아도 항상 안전하고 유효한 상태이다.
@ConfigurationProperties(prefix = "coffee") public class CoffeeProperties { private String name = "someone"; // 자바 자체의 초기값 private String place = "drinking ice coffee"; }
- List, Map, 중첩 객체 등 계층형 구조를 깔끔하게 매핑 가능하다.
-
@Valid 와 함께 사용하여 시작 시 설정값 검증 가능 (오류 발생 시 서버 중단) — 제일 중요
java
@ConfigurationProperties(prefix = "coffee") @Validated public class CoffeeProperties { @NotBlank private String name; // 반드시 값이 있어야 함 @Min(1) private int price; // 가격은 최소 1 이상이어야 함 }
3. @ConfigurationProperties 의 추가 기능 — 서드 파티 옵션
3-1. 서드 파티란?
📖
서드 파티(Third-party)는 '제3자'를 뜻한다. 원천 기술이나 서비스를 제공하는 당사자(퍼스트 파티)와
이를 이용하는 소비자(세컨드 파티)가 아닌, 외부의 다른 개발자나 기업을 의미 (from 나무위키)
Spring 관점의 서드 파티
Spring 관점의 서드 파티
- JDBC / ORM
- Auth0, Okta
- Lombok, Swagger
- Redis, Kafka, AWS SDK
Spring 프로젝트에 외부 의존성을 추가하고 해당 의존성(컴포넌트) 문서를 참조해 스프링 빈을 생성할 때 @ConfigurationProperties 가 유용하게 사용된다.
3-2. 예제 컴포넌트 생성해서 적용해보기
Droid.java
java
@Getter @Setter public class Droid { // 외부에서 가져온 서드 파티 컴포넌트라고 가정 // getter, setter 를 통해 Reflection API 로 환경변수가 설정된다. private String id, description; }
SpringConfig.java — 빈 등록 (수동)
java
@Configuration public class SpringConfig { @Bean @ConfigurationProperties(prefix = "droid") // 외부 서드파티에 직접 붙일 수 없는 경우 사용 (원래는 클래스 위가 권장) Droid droid() { return new Droid(); // Setter 를 통해 객체의 설정값 주입 } }
application.properties
properties
droid.id=BB-8 droid.description=Small, rolling android, Doesn't drink milk.
테스트 코드
java
@SpringBootTest class CoffeeServiceTest { @Autowired private Droid droid; @Test void getCoffee() { System.out.println("드로이드 id : " + droid.getId()); System.out.println("드로이드 설명 : " + droid.getDescription()); } }
결과

이런 식으로 @ConfigurationProperties 를 이용하여 서드 파티 컴포넌트의 환경변수를 설정하고 사용 가능하다.
3-3. Setter 없이 생성자로만 빈 등록해서 사용하기 (서드 파티)
Droid.java — record 클래스로 변경
java
// record 클래스 (@Getter, @RequiredArgsConstructor 포함) public record Droid(String id, String description) { // droid.id(); / droid.description(); 처럼 접근 가능 } // ─── 아래 코드와 동일함 ─── @Getter public class Droid { private final String id, description; public Droid(String id, String description) { this.id = id; this.description = description; } }
3-3-1. @Value 사용
java
@Configuration public class SpringConfig { @Value("${droid.id:defaultId}") private String id; @Value("${droid.description:defaultDesc}") private String description; @Bean @ConfigurationProperties(prefix = "droid") Droid droid() { return new Droid(id, description); } }
💡
환경 변수를 가져와서 생성자를 통해 Bean 등록 (필요 변수)
결과

3-3-2. @ConfigurationProperties 사용
Droid.java 수정
java
@ConfigurationProperties(prefix = "droid") public record Droid(String id, String description) { }
해당 클래스를 환경변수에 이용한다는 @ConfigurationProperties 설정
SpringConfig.java
java
@Configuration // 방법 1. Droid 를 빈 등록 (이것만 쓰는 경우 Setter 필수 / @ConfigurationPropertiesScan 없어도 됨) @EnableConfigurationProperties(Droid.class) public class SpringConfig { // 방법 2. 직접 빈 등록 (Setter 없는 경우 보통 이 방법 사용 / @ConfigurationPropertiesScan 필수) @Bean Droid droid(Droid dro) { return new Droid(dro.id(), dro.description()); } }
방법 한 개만 써야 중복 빈 등록이 방지된다. (상황에 맞게 사용)
결과

📌
정리
💡 결론 : 웬만하면
- Setter 가 있으면 :
@ConfigurationPropertiesScan없어도 됨. 여러 방법 존재 (위 SpringConfig.java 참고) - 생성자를 통해 초기화해야 하면 :
@ConfigurationPropertiesScan필수 (환경 변수 주입이 필요하기 때문)
💡 결론 : 웬만하면
@ConfigurationPropertiesScan 사용하면서 생성자를 통해 초기화하자!!
4. .env 와 @ConfigurationProperties 사용 (TIP)
Greeting.java
java
@Getter @Setter @ConfigurationProperties(prefix = "greeting") public class Greeting { private String name; // GREETING_NAME 매핑 private String coffee; // GREETING_COFFEE 매핑 }
.env
GREETING_NAME=envName GREETING_COFFEE=envCoffee
application.properties
properties
# 실제 배포 시 환경 변수로 관리 (해당 파일이 GitHub 에 올라가야 하기 때문)
greeting.name=${GREETING_NAME:someone}
greeting.coffee=${GREETING_COFFEE:americano}
Intellij 설정 (화면 우측 상단의 editConfiguration 설정을 하면 된다.)

결과

5. 후기
🙂
기존까지는 단순히
@Value 를 쓴 경험뿐이었다.
대부분의 프로젝트나 실습 때 환경변수를 단순히 가져다만 썼기 때문이라고 생각한다.@ConfigurationProperties 를 쓰는 방법도 알았으니
앞으로 더 복잡한 변수 관리가 필요할 때 유용하게 사용할 수 있을 것 같다.
'Spring boot' 카테고리의 다른 글
| [Spring] 테스트 코드 알아보기 (1) | 2026.07.22 |
|---|