생계유지형 개발자/Spring Framework

[Spring/Kotlin] 변경 불가능한 프로퍼티 변수 바인딩 Immutable Property Binding (Feat. @ConfigurationProperties)

이 가을 2021. 5. 4. 20:14

Property Binding (Normal)

스프링에서 빌드 환경에 따라 변경되는 상수값을 사용하기 위해 보편적으로 application.yml (또는 .property)에 문자열을 작성하고 소스코드에서 @Value 또는 @ConfigurationProperties 애노테이션을 사용해 변수에 바인딩 시킨다.

나는 주로 아웃바운드 API 주소를 개발환경과 운영환경에 달리 설정해주기 위해 사용한다.

YML 설정파일

### application-dev.yml

api:
  calendar: http://alpha-api.calendar.example.com:5001/connect/schedules
  map:
    url: https://map-dev.example.com/api/v1/auth/room
      api-key: x-map-api-key
      api-value: ba2b5e62-6665-47c2-a9e9-00b04167da84


### application-real.yml

api:
  calendar: http://api.calendar.example.com:5001/connect/schedules
  map:
    url: https://map.example.com/api/v1/auth/room
      api-key: x-map-api-key
      api-value: ce2e4b88-8086-47c2-0812-10e48391fa11

소스코드 (Kotlin)

@Component
@ConfigurationProperties(prefix="api")
data class ApiUrlProperty (
	var calendar: String = "",
    	var map: Map<String, String> = mapof()
) {
	val mapApiUrl by lazy { map["url"] }
	val mapApiKey by lazy { map["api-key"] }
	val mapApiValue by lazy { map["api-value"] }
    
}

 

Mutable Property Binding

하지만 위와 같은 기존의 방식은 치명적인 단점이 있다.

스플링 부트가 실행되면서 변수에 상수값을 바인딩할지라도, var 지시자를 사용해야 하기 때문에 calendar에 바인딩 된 문자열은 언제든지 바뀔 수 있다는, 즉, Mutable 하다는 것이다.

이는 Java로 구현하더라도 마찬가지이다.

왜냐하면 프로퍼티를 바인딩하는 시점이 ApiUrlProperty라는 클래스가 초기화할 때가 아닌, Setter 함수를 기반으로 바인딩하기 때문이다.

* Setter 함수를 사용한다는 것 자체가, 다른 객체에서 ApiUrlProperty 클래스 객체의 setter 호출이 가능하다는 의미

 

Immutable Property Binding

스프링 부트 2.2 이상에서는 @ConstructorBinding 애노테이션을 사용함으로써 위의 단점을 보완했다.

@ConstructorBinding 애노테이션은 스프링에게 setter 대신, 생성자를 사용해서 프로퍼티를 바인딩하라고 지시한다.

그렇게 되면 바인딩 되는 변수는 더 이상 var 지시자일 필요가 없다. 

val 지시자로 명시하면 된다. (자바라면 setter 함수를 생성하지 않아도 된다.)

소스코드 (Kotlin)

// @Component 안 지우면 에러남
@ConfigurationProperties(prefix="api")
@ConstructorBinding
data class ApiUrlProperty (
	val calendar: String = "",
    	val map: Map<String, String> = mapof()
) {
	val mapApiUrl by lazy { map["url"] }
	val mapApiKey by lazy { map["api-key"] }
	val mapApiValue by lazy { map["api-value"] }
    
}

 

@ConstructorBinding 을 사용하려면 @EnableConfigurationProperties 또는 @EnableConfigurationPropertiesScan 애노테이션을 반드시 작성해주어야 한다.

그리고 다른 enabler인 @Bean, @Component, @Import 와 함께 사용할 수 없다. 

@SpringBootApplication
@ComponentScan("com.example.jolly-sally")
@EnableConfigurationProperties(ApiUrlProperty::class)
class SampleApplication()

fun main(args: Array<String>) {
    runApplication<SampleApplication>(*args)
}

 


출처

springframework.guru/immutable-property-binding/

 

Immutable Property Binding - Spring Framework Guru

The Spring Framework has support for immutable property binding. This is considered a best practice since the properties cannot change once set. Learn more!

springframework.guru

github.com/spring-projects/spring-boot/issues/18674

 

Enabling configuration properties scanning by default prevents conditional registration of @ConfigurationProperties-annoted type

Hello, We were trying to migrate from 2.1.9 to 2.2.0 for our applications last week and encountered a startup failure due to a configuration validation failure. In short, if we are using @EnableCon...

github.com

cheese10yun.github.io/immutable-properties/

 

스프링 Immutable으로 Properties 설정하기 - Yun Blog | 기술 블로그

스프링 Immutable으로 Properties 설정하기 - Yun Blog | 기술 블로그

cheese10yun.github.io