DTO 생성자 표준 원칙 3에서 「가장 위험한 것은 조용한 되돌아감이다」라는 절이 금지 목록의 근거다.

@NoArgsConstructor나 @Setter가 하나라도 붙어 있으면, Jackson은
빈 객체를 만들고 필드를 하나씩 채우는 경로를 쓸 수 있다.
에러도, 경고도 없다.

@Setter·@NoArgsConstructor·@Data를 DTO에 쓰지 않는 규칙이 이 문장 위에 서 있다. 표준을 증명하는 방식의 시범 실험 셋 중 하나다.


주장 — 표준의 ❌ 예시는 경고 없이 기본 생성자와 setter로 만들어진다

claim.md에는 이렇게 적었다.

@Getter·@Setter·@NoArgsConstructor·@AllArgsConstructor를 붙인 class는,
Spring Boot가 설정한 ObjectMapper가 예외도 경고도 없이
기본 생성자로 빈 객체를 만들고 값을 채우는 경로로 역직렬화한다.

여기서 표준 글보다 한 걸음 좁게 적었다. 표준은 「쓸 수 있다」인데 나는 「쓴다」로 적었다. 이 차이가 결과에서 그대로 드러났다(「예상과 달랐던 점」).


반증 조건 — 전 인자 생성자로 가거나 경고가 한 건이라도 나면 반증이다

돌리기 전에 적은 선이다.

  • refuted — 두 환경 중 하나라도 부분 입력에서 quantity=null(전 인자 생성자 경로), 역직렬화 예외, WARN 이상 로그 1건 이상 중 하나가 나온다
  • supported — 두 환경 모두 부분 입력 quantity=7, 정상 입력 mouse,3, WARN 0건
  • inconclusive — 그 밖의 경우

설계 — 필드 초기값 7로 어느 경로로 만들어졌는지 가른다

대상 타입은 표준 글의 ❌ 예시 그대로다. 한 곳만 더했다.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SetterRequest {
    private String productName;
    private Integer quantity = 7;   // ← 경로를 가르는 표식
}

그리고 quantity가 빠진 JSON {"productName":"mouse"}를 넣는다.

기본 생성자 경로   new SetterRequest() → 초기값 7이 산다 → setProductName   → quantity=7
전 인자 생성자 경로 new SetterRequest("mouse", null)  → 생성자가 null을 넣는다 → quantity=null

호출 스택을 뒤지지 않고 결과값 하나로 경로가 갈린다. 경고는 역직렬화 두 번 동안 Logback 루트 로거에 수집기를 붙여 WARN 이상 이벤트 수를 셌다.

매퍼와 환경은 생성자 감지 실험과 같다. Boot가 등록한 매퍼, Boot Gradle 플러그인, Boot 3.5.16과 4.1.1이다. Lombok은 각 Boot가 관리하는 버전(둘 다 1.18.46)을 썼다.


결과 — Boot 3.5는 setter로, Boot 4.1은 전 인자 생성자로 만들었고 경고는 둘 다 0건이었다

run.sh가 쓴 원본 출력이다.

PROBE_ENV boot=3.5.16 lombok=1.18.46
PROBE boot=3.5.16 jackson=2.21.4 java=21.0.9 params=true partial=ok:mouse,7 full=ok:mouse,3 warns=0
PROBE_ENV boot=4.1.1 lombok=1.18.46
PROBE boot=4.1.1 jackson=3.1.5 java=21.0.9 params=true partial=ok:mouse,null full=ok:mouse,3 warns=0
  • Boot 3.5.16quantity=7. 기본 생성자와 setter 경로다. 표준 글이 경고한 모양 그대로다
  • Boot 4.1.1quantity=null. 전 인자 생성자 경로다
  • 경고 — 두 버전 모두 0건. 정상 입력은 두 버전 모두 값이 다 채워졌다

판정은 refuted다. Boot 4.1.1이 반증 조건의 첫 항목(quantity=null)에 걸렸다.


예상과 달랐던 점 — 반증은 규칙이 아니라 내가 좁혀 적은 주장에서 나왔다

표준 글의 문장을 다시 대보면 이렇다.

표준 글의 문장 Boot 3.5.16 Boot 4.1.1
setter 경로를 쓸 수 있다 썼다 안 썼다
에러도 경고도 없다 0건 0건

표준이 경고한 「조용하다」는 두 버전 모두에서 맞았다. 반증이 난 것은 내가 「쓸 수 있다」를 「쓴다」로 좁혀 적었고, Boot 4.1이 다른 경로를 골랐기 때문이다.

그래도 판정을 고치지 않았다. 결과를 보고 반증 조건을 바꾸면 선을 먼저 그은 의미가 사라진다(표준을 증명하는 방식 원칙 8). 설계를 고쳐 다시 재야 한다면 새 실험으로 만든다.

뜻밖에 하나가 더 보였다. Boot 4.1에서는 필드 초기값이 조용히 사라졌다. JSON에 없는 필드가 기본값 7이 아니라 null이 됐다. 같은 DTO가 Boot를 올리는 것만으로 다른 값을 갖게 된다. 규칙이 막으려던 「조용한 되돌아감」과는 다른 모양의 조용한 변화다. 이 실험의 주장 밖이라 판정에는 넣지 않았다.


이 결과가 표준에 남기는 것 — 문장을 버전 조건과 함께 다시 적을지 이슈에서 정한다

표준 글 원칙 3에 > 반증: 줄을 달고, 생성자 감지·Lombok final 필드 실험과 함께 kind: 모순 이슈 하나로 넘겼다.

이슈에서 가를 것은 둘이다. 실험의 결함 — 주장을 좁혀 적은 것 — 인지, 규칙의 결함 — 경로가 버전마다 달라 「setter로 되돌아간다」는 설명이 낡은 것 — 인지다. 금지 목록 자체는 두 버전 모두에서 「경고 없이 통과한다」가 확인됐으므로 이 실험만으로는 흔들리지 않는다.


정리

  • 경고는 두 버전 모두 0건이다. 표준이 말한 「조용하다」는 맞았다
  • 경로는 버전마다 달랐다. Boot 3.5.16은 기본 생성자+setter, Boot 4.1.1은 전 인자 생성자
  • 반증은 「쓸 수 있다」를 「쓴다」로 좁혀 적은 주장에서 났다. 판정은 고치지 않는다
  • Boot 4.1에서는 JSON에 없는 필드의 초기값이 null로 덮였다. 주장 밖의 발견이다

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기