본문 바로가기
빌드노트 검색

빌드노트

Spring Boot의 Duration 설정에 30을 넣으면 30초일까

Spring Boot의 Duration 속성에서 단위 없는 30이 30ms로 바인딩되는 이유를 확인한다. 실제 getter 출력과 단위 선언 유무의 비교를 바탕으로 30s·PT30S를 쓰는 경우와 @DurationUnit으로 기존 숫자의 의미를 유지하는 경우를 구별한다.

Spring Boot의 Duration 구성 속성에 client.timeout=30을 넣었다고 해서 30초가 되는 것은 아니다. 기본 변환에서는 단위 없는 수를 밀리초로 읽는다. 초 단위 선언이 따로 없다면 30은 0.03초다. 30초가 목적이라면 30s나 PT30S처럼 값에 단위를 명시한다.

설정 문자열보다 바인딩된 값을 먼저 본다

먼저 실제 타임아웃을 사용하는 코드에 주입된 설정 객체를 확인한다. 여기서는 @ConfigurationProperties("client")로 등록한 JavaBean에 Duration timeout과 getter/setter가 있는 경우를 예로 든다. clientProperties는 그 빈을 주입받은 변수다. 기존 소비 메서드에 다음 두 줄을 넣으면 변환이 끝난 값을 볼 수 있다.

var timeout = clientProperties.getTimeout();
System.out.println("bound=" + timeout + ", millis=" + timeout.toMillis());

별도 최소 컨텍스트에서 단위 선언 없이 client.timeout=30을 바인딩한 뒤 이 코드를 실행하자 다음과 같이 나왔다.

bound=PT0.03S, millis=30

PT0.03S는 0.03초다. JDK의 Duration 문서 (새 탭에서 열림)에 따르면 toMillis()는 전체 길이를 밀리초로 환산한다. 설정 파일의 문자열 30을 다시 출력하는 것보다 실제 객체가 30ms인지 30초인지 구별하기 쉽다.

30초를 의도했다면 해당 설정 키의 값을 다음처럼 바꾼다. 키 이름은 자기 설정 객체의 prefix와 속성 이름에 맞춘다.

client.timeout=30s

로컬 앱을 다시 시작해 같은 getter를 확인한다. 별도 컨텍스트에서도 이 입력으로 다시 바인딩한 결과는 bound=PT30S, millis=30000이었다. client.timeout=PT30S로 써도 같은 값이 나왔다.

여전히 30ms라면 실제 적용된 설정값과 사용 중인 빈이 수정 대상과 같은지 확인한다. 이미 PT30S, millis=30000인데 요청이 더 빨리 끝난다면 이 바인딩 결과만으로 원인을 설명할 수 없다. 그다음에는 해당 값을 전달받는 클라이언트 설정을 확인해야 한다.

같은 30도 선언에 따라 달랐다

Spring Boot의 Duration 변환 문서 (새 탭에서 열림)는 기본 단위가 밀리초이며 @DurationUnit으로 바꿀 수 있다고 설명한다. 숫자와 접미 단위를 결합한 형식과 ISO-8601 형식도 지원한다.

이를 비교하려고 Spring Boot 3.5.7의 @EnableConfigurationProperties만 올린 최소 Spring 컨텍스트를 만들었다. HTTP 서버는 띄우지 않았다. 단위 선언이 없는 빈과 필드에 @DurationUnit(ChronoUnit.SECONDS)가 있는 빈에 같은 문자열을 각각 넣어 getter 반환값을 확인했다.

  • 단위 선언 없음: 30은 PT0.03S·30ms, 30s와 PT30S는 각각 PT30S·30000ms였다.
  • SECONDS 선언 있음: 30, 30s, PT30S가 모두 PT30S·30000ms였다.
  • 입력이 30ms: 선언이 있든 없든 PT0.03S·30ms였다.

즉, 초 단위 선언이 있어도 30ms를 30초로 바꾸지는 않았다. DurationUnit API (새 탭에서 열림)는 입력에 단위가 없을 때 사용할 단위를 지정한다고 설명한다. 값을 읽는 사람에게도 단위가 보이게 하려면 설정에 30s 또는 30ms를 적는 편이 분명하다.

필드 초기값과 입력의 기본 단위는 다르다

두 실험 빈의 초기값은 모두 Duration.ofSeconds(30)이었다. 속성을 아예 주지 않으면 두 빈 모두 이 초기값을 유지해 PT30S·30000ms가 나왔다. 하지만 단위 선언이 없는 빈에 문자열 30을 주자 30ms로 바뀌었다. 초 단위 초기값을 썼다는 사실은 새 입력의 해석 단위를 정하지 않았다.

기존 설정의 단위 없는 숫자를 계속 초로 받아야 한다면, JavaBean 방식에서는 해당 필드에 다음처럼 기본 단위를 선언할 수 있다. 기존 클래스의 Duration import와 getter/setter를 유지하고, DurationUnit은 org.springframework.boot.convert, ChronoUnit은 java.time.temporal에서 import한다.

@DurationUnit(ChronoUnit.SECONDS)
private Duration timeout = Duration.ofSeconds(30);

이미 밀리초로 사용하던 단위 없는 값도 이 선언을 추가하면 초로 읽히므로, 기존 값의 의미를 먼저 확인한다. 새 값을 작성할 수 있다면 30s처럼 단위를 붙여 의도를 드러내면 된다. 생성자 바인딩은 위 필드 예제를 그대로 적용하지 말고 공식 문서의 생성자 매개변수 선언을 확인한다.

이 관찰은 Windows, OpenJDK 17.0.2, Spring Boot 3.5.7, Spring Framework 6.2.12에서 얻었다. 참조한 3.5 계열 문서의 표시 버전은 3.5.16으로 실행 버전과 다르다. 예제에서는 외부 설정을 읽지 않고 정해진 문자열만 주입했다. 생성자 바인딩, 사용자 정의 변환기와 실제 HTTP·DB 타임아웃은 실행하지 않았다. 확인한 것은 기본 속성 바인딩이 만든 Duration이며, 외부 요청이 정확히 그 시간에 중단된다는 측정은 아니다.

← 전체 글 보기