[Spring] Spring Formatter

이연우·2025년 8월 18일

TIL

목록 보기
81/100

🧮 FormattingConversionService란?

  • ConversionService + Formatter를 결합한 구현체
  • 타입 변환과 포맷팅을 한 곳에서 처리하도록 설계되어,
    다양한 타입 변환/포맷을 쉽게 적용할 수 있음

🔗 FormattingConversionService

  • Formatter를 지원하는 ConversionService
  • 어댑터 패턴으로 FormatterConverter처럼 동작하도록 연결됨

⚙️ DefaultFormattingConversionService

  • FormattingConversionService통화/숫자 관련 기본 Formatter가 추가된 버전
  • 역할
    • ConversionService 구현체로 동작
    • 내부적으로 다양한 Converter/Formatter 등록·관리
  • 포인트
    • ConverterRegistry를 통해 Converter/Formatter를 등록
    • 등록된 항목은 convert() 호출 시 자동으로 선택·적용

🧪 테스트 코드 흐름

DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

// Converter 등록
conversionService.addConverter(new StringToPersonConverter());
conversionService.addConverter(new PersonToStringConverter());
// Formatter 등록
conversionService.addFormatter(new PriceFormatter());

// 변환
String result = conversionService.convert(10000, String.class);
// => "10,000"
  • ConversionServiceconvert()만 호출하면 됨
  • 어떤 Converter/Formatter가 쓰였는지는 서비스 내부에서 자동 선택
    → 즉, 컨버터/포매터의 구체 구현에 의존하지 않음

🚀 Spring Boot에서의 기본 동작

  • 스프링 부트는 기본적으로 WebConversionService를 사용
  • 이는 DefaultFormattingConversionService를 상속
  • 따라서 웹 바인딩 전반에 변환/포맷팅 기능이 기본 적용됨

🏷️ Spring이 제공하는 Formatter (어노테이션 기반)

  • DTO 필드 단위로 원하는 포맷을 지정할 수 있음
  • @NumberFormat → 숫자 포맷 지정
    (내부적으로 NumberFormatAnnotationFormatterFactory)

  • @DateTimeFormat → 날짜/시간 포맷 지정
    (내부적으로 Jsr310DateTimeFormatAnnotationFormatterFactory)

@Data // (예시 전용 — 운영 코드에서는 지양)
public class FormatForm {
    @NumberFormat(pattern = "#,###.##")
    private BigDecimal price;

    @DateTimeFormat(pattern = "dd-MM-yyyy")
    private LocalDate orderDate;
}
@RestController
public class FormatController {
    @PostMapping("/format")
    public ResponseEntity<String> format(@ModelAttribute FormatForm form) {
        return new ResponseEntity<>(
            "form.getPrice() = " + form.getPrice() +
            " form.getOrderDate() = " + form.getOrderDate(),
            HttpStatus.OK
        );
    }
}

→ 결과: 필드마다 지정한 포맷으로 정상 변환/바인딩

📦 JSON과 포맷팅 (중요 구분)

  • @NumberFormat / @DateTimeFormat
    폼 데이터/URL 파라미터에 적용되는 ConversionService 영역
  • Jackson(JSON 직렬화/역직렬화)에는 기본적으로 영향 없음

→ JSON 포맷을 바꾸려면 @JsonFormat 또는 커스텀 JsonDeserializer를 사용해야 됨

🧰 커스텀 Deserializer 예시

public class CurrencyDeserializer extends JsonDeserializer<BigDecimal> {
    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = p.getText().replaceAll("[^\\d.]", ""); // 숫자/소수점만 남기기
        return new BigDecimal(value);
    }
}

public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
    @Override
    public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        return LocalDate.parse(p.getText(), DateTimeFormatter.ofPattern("dd-MM-yyyy"));
    }
}
@Data // (예시 전용 — 운영 코드에서는 지양)
public class JsonFormatDto {
    @JsonDeserialize(using = CurrencyDeserializer.class)
    private BigDecimal price;

    @JsonDeserialize(using = LocalDateDeserializer.class)
    private LocalDate orderDate;
}
@PostMapping("/json-format/deserialize")
public ResponseEntity<String> jsonFormatDeserialize(@RequestBody JsonFormatDto dto) {
    return new ResponseEntity<>(
        "dto.getPrice() = " + dto.getPrice() +
        " dto.getOrderDate() = " + dto.getOrderDate(),
        HttpStatus.OK
    );
}

→ 결과: JSON 본문도 커스텀 룰로 정상 변환

🗓️ @JsonFormat 조합 예시

@Data // (예시 전용 — 운영 코드에서는 지양)
public class JsonFormatDtoV2 {

    @JsonDeserialize(using = CurrencyDeserializer.class)
    private BigDecimal price;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
    private LocalDate orderDate;
}
@PostMapping("/json-format/annotation")
public ResponseEntity<String> jsonFormatAnnotation(@RequestBody JsonFormatDtoV2 dto) {
    return new ResponseEntity<>(
        "dto.getPrice() = " + dto.getPrice() +
        " dto.getOrderDate() = " + dto.getOrderDate(),
        HttpStatus.OK
    );
}

@JsonFormat으로 날짜 포맷 지정 가능
콤마 포함 숫자는 Jackson이 자동 처리하지 않으므로 커스텀 처리 필요
→ 커스텀 데이터 포맷은 Deserializer 사용으로 해결


🧠 요약 정리

구분핵심 내용사용 포인트
FormattingConversionServiceConversionServiceFormatter 지원 결합변환 + 포맷을 한 번에 처리,
어댑터 패턴으로 Formatter를 Converter처럼 사용
DefaultFormattingConversionService통화/숫자 Formatter 추가된 기본 구현addConverter, addFormatter
손쉽게 확장
테스트 흐름등록 → convert() 호출 → 결과컨버터/포매터 종류는
내부에서 자동 선택
Spring Boot 기본WebConversionService 사용
(상속 구조)
웹 바인딩 전반에 기본 적용
어노테이션 포맷팅@NumberFormat,
@DateTimeFormat
폼/쿼리 파라미터 변환(ConversionService 영역)
JSON 포맷팅@JsonFormat 또는
JsonDeserializer
Jackson 영역. 숫자 콤마 등은
커스텀 Deserializer 필요
핵심 차이ConversionService vs Jackson바인딩 계층이 다름
목적에 맞는 도구 선택

0개의 댓글