🧮 FormattingConversionService란?
ConversionService+Formatter를 결합한 구현체- 타입 변환과 포맷팅을 한 곳에서 처리하도록 설계되어,
다양한 타입 변환/포맷을 쉽게 적용할 수 있음
🔗 FormattingConversionService
Formatter를 지원하는 ConversionServiceFormatter가 Converter처럼 동작하도록 연결됨⚙️ DefaultFormattingConversionService
FormattingConversionService에 통화/숫자 관련 기본 Formatter가 추가된 버전ConversionService 구현체로 동작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"
ConversionService의 convert()만 호출하면 됨🚀 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은ConversionService 영역→ 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 사용으로 해결
🧠 요약 정리
| 구분 | 핵심 내용 | 사용 포인트 |
|---|---|---|
| FormattingConversionService | ConversionService에 Formatter 지원 결합 | 변환 + 포맷을 한 번에 처리, 어댑터 패턴으로 Formatter를 Converter처럼 사용 |
| DefaultFormattingConversionService | 통화/숫자 Formatter 추가된 기본 구현 | addConverter, addFormatter로손쉽게 확장 |
| 테스트 흐름 | 등록 → convert() 호출 → 결과 | 컨버터/포매터 종류는 내부에서 자동 선택 |
| Spring Boot 기본 | WebConversionService 사용(상속 구조) | 웹 바인딩 전반에 기본 적용 |
| 어노테이션 포맷팅 | @NumberFormat,@DateTimeFormat | 폼/쿼리 파라미터 변환(ConversionService 영역) |
| JSON 포맷팅 | @JsonFormat 또는 JsonDeserializer | Jackson 영역. 숫자 콤마 등은 커스텀 Deserializer 필요 |
| 핵심 차이 | ConversionService vs Jackson | 바인딩 계층이 다름 목적에 맞는 도구 선택 |