๐Ÿ“„ Spring ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ด€๋ฆฌ: @Value vs @ConfigurationProperties

CH.devยท2025๋…„ 7์›” 25์ผ
post-thumbnail

Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์™ธ๋ถ€ ์„ค์ •(properties, yml) ๊ฐ’์€ ํ•„์ˆ˜์ ์ž„. ์ด ๊ฐ’๋“ค์„ ์ฝ”๋“œ์— ์ฃผ์ž…ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ @Value์™€ @ConfigurationProperties๊ฐ€ ์žˆ์Œ. ๋‘˜์˜ ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•จ.

๐Ÿ’ก ํ•ต์‹ฌ ๊ฐœ๋…

@Value: ๊ฐœ๋ณ„ ๊ฐ’์˜ ๋‹จ์ˆœ ์ฃผ์ž…

@Value๋Š” SpEL(Spring Expression Language)์„ ์ด์šฉํ•ด ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ํ•˜๋‚˜์”ฉ ํ•„๋“œ๋‚˜ ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ฃผ์ž…ํ•˜๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์‹์ž„.

  • ์žฅ์ : ๋ช‡ ๊ฐœ ์•ˆ ๋˜๋Š” ๊ฐ„๋‹จํ•œ ๊ฐ’์„ ์ฃผ์ž…ํ•  ๋•Œ ๊ฐ€์žฅ ๋น ๋ฅด๊ณ  ์ง๊ด€์ ์ž„.
  • ๋‹จ์ :
    • ๋ถ„์‚ฐ๋œ ๊ด€๋ฆฌ: ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด @Value ์–ด๋…ธํ…Œ์ด์…˜์ด ์—ฌ๋Ÿฌ ํด๋ž˜์Šค์— ํฉ์–ด์ ธ ์ถ”์ ๊ณผ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง.
    • ํƒ€์ž… ๋ถˆ์•ˆ์ •์„ฑ: ๋Ÿฐํƒ€์ž„์— ๊ฐ’์„ ์ฃผ์ž…ํ•˜๋ฏ€๋กœ, ์ž˜๋ชป๋œ ํƒ€์ž…์˜ ๊ฐ’์ด ๋“ค์–ด์™€๋„ ์ปดํŒŒ์ผ ์‹œ์ ์— ์•Œ ์ˆ˜ ์—†์Œ.
    • ์˜คํƒ€์— ์ทจ์•ฝ: ํ”„๋กœํผํ‹ฐ ํ‚ค๋ฅผ ๋ฌธ์ž์—ด("${...}")๋กœ ์ž‘์„ฑํ•˜์—ฌ ์˜คํƒ€๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์ฐพ๊ธฐ ์–ด๋ ค์›€.
    • ๋ณต์žกํ•œ ๊ตฌ์กฐ ํ•œ๊ณ„: List๋‚˜ Map ๊ฐ™์€ ๋ณต์žกํ•œ ์ž๋ฃŒ๊ตฌ์กฐ๋‚˜ ๊ณ„์ธต์  ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ๋ถˆํŽธํ•จ.

@ConfigurationProperties: ๊ตฌ์กฐํ™”๋œ ์„ค์ • ๊ฐ์ฒด ๋ฐ”์ธ๋”ฉ

@ConfigurationProperties๋Š” ํŠน์ • ์ ‘๋‘์‚ฌ(prefix)๋ฅผ ๊ฐ€์ง„ ํ”„๋กœํผํ‹ฐ๋“ค์„ ํ•˜๋‚˜์˜ ๊ฐ์ฒด(POJO/Record)์— ํ†ต์งธ๋กœ ๋ฐ”์ธ๋”ฉํ•˜๋Š”, ํ˜„๋Œ€ Spring Boot์—์„œ ๊ฐ€์žฅ ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ์‹์ž„.

  • ์žฅ์ :
    • ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ์•ˆ์ •์„ฑ: ์ปดํŒŒ์ผ ์‹œ์ ์— ๋Œ€์ƒ ํด๋ž˜์Šค์˜ ํ•„๋“œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜๋˜๋ฏ€๋กœ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฏธ๋ฆฌ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Œ.
    • ๊ตฌ์กฐํ™” ๋ฐ ์บก์Аํ™”: ๊ด€๋ จ ์„ค์ •๋“ค์ด ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ ๋ฌถ์—ฌ, ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜์˜ํ•˜๋ฏ€๋กœ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๊ทน๋Œ€ํ™”๋จ.
    • ๋ถˆ๋ณ€์„ฑ(Immutability) ํ™•๋ณด: ์ƒ์„ฑ์ž ๋ฐ”์ธ๋”ฉ์„ ํ†ตํ•ด final ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ. (Setter๊ฐ€ ํ•„์š” ์—†์–ด์ง)
    • ๊ฐ•๋ ฅํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: @Validated์™€ JSR-303(@NotEmpty, @Max ๋“ฑ) ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๊ฐ ํ”„๋กœํผํ‹ฐ ๊ฐ’์˜ ์œ ํšจ์„ฑ์„ ์„ ์–ธ์ ์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Œ.
    • IDE ์ง€์›: spring-boot-configuration-processor ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด application.yml ์ž‘์„ฑ ์‹œ ํ‚ค ์ž๋™์™„์„ฑ ๋ฐ ๋ฌธ์„œ๋ณด๊ธฐ๋ฅผ ์ง€์›ํ•จ.

โ€ป ๋“ฑ๋ก ๋ฐ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

@ConfigurationProperties๊ฐ€ ๋ถ™์€ ํด๋ž˜์Šค๋ฅผ Spring ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ธ์‹ํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ฃผ๋กœ ๋‘ ๊ฐ€์ง€์ž„.

  1. @Component ์‚ฌ์šฉ: ์„ค์ • ํด๋ž˜์Šค์— @Component๋ฅผ ๋ถ™์—ฌ ์ปดํฌ๋„ŒํŠธ ์Šค์บ” ๋Œ€์ƒ์œผ๋กœ ๋งŒ๋“ฆ. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜์ง€๋งŒ, ์„ค์ • ํด๋ž˜์Šค๊ฐ€ ๋‹ค๋ฅธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ปดํฌ๋„ŒํŠธ์™€ ์„ž์ด๋Š” ๋‹จ์ ์ด ์žˆ์Œ.
  2. @EnableConfigurationProperties ์‚ฌ์šฉ: @Configuration์ด ๋ถ™์€ ์„ค์ • ํด๋ž˜์Šค๋‚˜ ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค์— @EnableConfigurationProperties(MyProperties.class)๋ฅผ ๋ช…์‹œํ•˜์—ฌ ๋“ฑ๋กํ•จ. ์„ค์ •์˜ ์ฑ…์ž„๊ณผ ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์ด ๋ฐฉ์‹์ด ๋” ๊ถŒ์žฅ๋จ.

๐Ÿ“Š @Value vs @ConfigurationProperties ๋น„๊ต

๊ตฌ๋ถ„@Value@ConfigurationProperties
๋ฐ”์ธ๋”ฉ ๋‹จ์œ„๊ฐœ๋ณ„ ํ”„๋กœํผํ‹ฐ (๋‚ฑ๊ฐœ)ํ”„๋กœํผํ‹ฐ ๊ทธ๋ฃน (๊ฐ์ฒด)
์ปดํŒŒ์ผ ์‹œ์  ํƒ€์ž… ์•ˆ์ •์„ฑ๋‚ฎ์Œ (๋Ÿฐํƒ€์ž„ ์ฃผ์ž…)๋†’์Œ (๊ฐ์ฒด ํ•„๋“œ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜)
๊ตฌ์กฐ์  ๋ฐ”์ธ๋”ฉ๋ถ€์ ํ•ฉ์ ํ•ฉ (๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์ค‘์ฒฉ ํด๋ž˜์Šค๋กœ ๋งคํ•‘)
๋ถˆ๋ณ€์„ฑ(Immutability)๋ถˆ๊ฐ€๋Šฅ๊ฐ€๋Šฅ (์ƒ์„ฑ์ž ๋ฐ”์ธ๋”ฉ)
์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์ง์ ‘ ๊ตฌํ˜„ ํ•„์š”@Validated๋กœ ์„ ์–ธ์  ์ง€์›
IDE ์ž๋™์™„์„ฑ๋ฏธ์ง€์›์ง€์› (์˜์กด์„ฑ ์ถ”๊ฐ€ ์‹œ)
๊ถŒ์žฅ ์‚ฌ์šฉ์ฒ˜๋‹จ์ผ ๊ฐ’, ์ž„์‹œ/ํ…Œ์ŠคํŠธ ์„ค์ •์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ, ๋ณต์žก/๊ณ„์ธต์  ์„ค์ •

๐Ÿง  ์ฝ”๋“œ ์˜ˆ์‹œ

๊ณ„์ธต์ ์ธ application.yml ์˜ˆ์ œ

API ์„œ๋ฒ„์˜ ์ธ์ฆ ์ •๋ณด, CORS ํ—ˆ์šฉ ๋ชฉ๋ก ๋“ฑ ๋ณตํ•ฉ์ ์ธ ์„ค์ •์„ ๊ด€๋ฆฌํ•˜๋Š” ์˜ˆ์ œ.

# application.yml
server:
  port: 8080

# API ๊ด€๋ จ ์„ค์ • ๊ทธ๋ฃน
api:
  # ์ค‘์ฒฉ ๊ตฌ์กฐ (auth)
  auth:
    jwt-secret-key: "a-very-long-and-secure-secret-key-for-development"
    token-expires-in: 3600 # 1์‹œ๊ฐ„ (์ดˆ ๋‹จ์œ„)
  # List ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ
  cors:
    allowed-origins:
      - "http://localhost:3000"
      - "https://my-frontend.com"

---
spring:
  config:
    activate:
      on-profile: prod

api:
  auth:
    # ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์™ธ๋ถ€ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋‚˜ Secret Manager๋ฅผ ํ†ตํ•ด ์ฃผ์ž…
    jwt-secret-key: ${API_JWT_SECRET_KEY}
    token-expires-in: 86400 # 24์‹œ๊ฐ„
  cors:
    allowed-origins:
      - "https://my-real-service.com"

@Value ์‚ฌ์šฉ ์˜ˆ์‹œ (๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹)

ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋Š˜์–ด๋‚ ์ˆ˜๋ก ์ƒ์„ฑ์ž๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๊ด€๋ฆฌ ํฌ์ธํŠธ๊ฐ€ ๋ถ„์‚ฐ๋จ.

@Service
public class SimpleAuthService {

    private final String jwtSecretKey;
    private final List<String> allowedOrigins;

    public SimpleAuthService(
            @Value("${api.auth.jwt-secret-key}") String jwtSecretKey,
            @Value("${api.cors.allowed-origins}") List<String> allowedOrigins) {
        this.jwtSecretKey = jwtSecretKey;
        this.allowedOrigins = allowedOrigins;
    }
    // ...
}

@ConfigurationProperties ์‚ฌ์šฉ ์˜ˆ์‹œ (๊ถŒ์žฅ ๋ฐฉ์‹)

1. ๋ถˆ๋ณ€(Immutable) ์„ค์ • ํ”„๋กœํผํ‹ฐ ํด๋ž˜์Šค ์ž‘์„ฑ (with ์ƒ์„ฑ์ž ๋ฐ”์ธ๋”ฉ)
Java 17 ์ด์ƒ์—์„œ๋Š” record๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Œ.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Positive;
import java.util.List;

@ConfigurationProperties(prefix = "api")
@Validated // ํ•„๋“œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ™œ์„ฑํ™”
public class ApiProperties {

    @NotEmpty // jakarta.validation.constraints.NotEmpty
    private final Auth auth;
    @NotEmpty
    private final Cors cors;

    // ์ƒ์„ฑ์ž ๋ฐ”์ธ๋”ฉ: Spring Boot๊ฐ€ ์ด ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด yml ๊ฐ’์„ ์ฃผ์ž…ํ•จ (Setter ๋ถˆํ•„์š”)
    public ApiProperties(Auth auth, Cors cors) {
        this.auth = auth;
        this.cors = cors;
    }

    // --- Getters ---
    public Auth getAuth() { return auth; }
    public Cors getCors() { return cors; }

    // --- ์ค‘์ฒฉ๋œ ์„ค์ •์„ ์œ„ํ•œ ์ •์ (static) ๋‚ด๋ถ€ ํด๋ž˜์Šค ---
    public static class Auth {
        @NotEmpty
        private final String jwtSecretKey;
        @Positive
        private final int tokenExpiresIn;

        public Auth(String jwtSecretKey, int tokenExpiresIn) {
            this.jwtSecretKey = jwtSecretKey;
            this.tokenExpiresIn = tokenExpiresIn;
        }

        public String getJwtSecretKey() { return jwtSecretKey; }
        public int getTokenExpiresIn() { return tokenExpiresIn; }
    }
    
    public static class Cors {
        @NotEmpty
        private final List<String> allowedOrigins;
        
        public Cors(List<String> allowedOrigins) {
            this.allowedOrigins = allowedOrigins;
        }
        
        public List<String> getAllowedOrigins() { return allowedOrigins; }
    }
}

2. ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ”„๋กœํผํ‹ฐ ํด๋ž˜์Šค ๋“ฑ๋ก

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(ApiProperties.class) // ์ž‘์„ฑํ•œ ํ”„๋กœํผํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋ก
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. ํ”„๋กœํผํ‹ฐ ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉ

@Service
public class AdvancedAuthService {

    private final ApiProperties apiProperties;

    // ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ApiProperties ๊ฐ์ฒด๋ฅผ ํ†ต์งธ๋กœ ์ฃผ์ž…๋ฐ›์Œ
    public AdvancedAuthService(ApiProperties apiProperties) {
        this.apiProperties = apiProperties;
    }

    public void issueToken() {
        String secretKey = apiProperties.getAuth().getJwtSecretKey();
        int expirySeconds = apiProperties.getAuth().getTokenExpiresIn();
        System.out.println("Using Secret Key (first 5 chars): " + secretKey.substring(0, 5) + "...");
        System.out.println("Token expires in: " + expirySeconds + " seconds");
        System.out.println("Allowed Origins: " + apiProperties.getCors().getAllowedOrigins());
    }
}

๐Ÿ” ๋” ๊นŠ์ด ํŒŒ๊ณ ๋“ค๊ธฐ

  • @ConfigurationPropertiesScan: Spring Boot 2.2๋ถ€ํ„ฐ ์ถ”๊ฐ€๋œ ์–ด๋…ธํ…Œ์ด์…˜. @EnableConfigurationProperties ๋Œ€์‹  ๋ฉ”์ธ ํด๋ž˜์Šค์— @ConfigurationPropertiesScan์„ ๋ถ™์ด๋ฉด ์ง€์ •๋œ ํŒจํ‚ค์ง€ ๋‚ด์˜ @ConfigurationProperties๋ฅผ ์ž๋™์œผ๋กœ ์Šค์บ”ํ•˜์—ฌ ๋“ฑ๋กํ•ด์คŒ. ํ›จ์”ฌ ํŽธ๋ฆฌํ•จ.
  • Spring Boot Configuration Processor: build.gradle์— annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด, IDE๊ฐ€ yml/properties ํŒŒ์ผ์—์„œ api.auth.jwt-secret-key ๊ฐ™์€ ํ‚ค๋ฅผ ์ž๋™์™„์„ฑํ•˜๊ณ , Javadoc ์ฃผ์„์„ ํˆดํŒ์œผ๋กœ ๋ณด์—ฌ์ค˜ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•จ.
  • Externalized Configuration ์šฐ์„ ์ˆœ์œ„: Spring Boot๋Š” ๋‹ค์–‘ํ•œ ์†Œ์Šค์—์„œ ์„ค์ •์„ ๋กœ๋“œํ•˜๋ฉฐ ๋ช…ํ™•ํ•œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง. (e.g., ์ปค๋งจ๋“œ๋ผ์ธ ์ธ์ž > OS ํ™˜๊ฒฝ๋ณ€์ˆ˜ > application.yml ์™ธ๋ถ€ ํŒŒ์ผ > application.yml ๋‚ด๋ถ€ ํŒŒ์ผ). ์ด ์ˆœ์„œ๋ฅผ ์ดํ•ดํ•˜๋ฉด ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ ์„ค์ • ์ถฉ๋Œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋จ.
profile
๋” ์ด์ƒ ๋ฏธ๋ฃฐ ์ˆ˜ ์—†๋‹ค ๋‚˜์˜ ๊ณต๋ถ€ ๋‚˜์˜ ์„ฑ์žฅ

0๊ฐœ์˜ ๋Œ“๊ธ€