Loading...
Spring Framework Reference Documentation 7.0.2의 Spring Field Formatting의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
이전 섹션에서 논의했듯이, core.convert는 범용 타입 변환 시스템입니다. 이 시스템은 통합된 ConversionService API와 한 타입에서 다른 타입으로의 변환 로직을 구현하기 위한 강하게 타입이 지정된 Converter SPI를 제공합니다. Spring 컨테이너는 이 시스템을 사용하여 bean 프로퍼티 값들을 바인딩합니다. 추가로, Spring Expression Language (SpEL)와 DataBinder 모두 이 시스템을 사용하여 필드 값들을 바인딩합니다.
예를 들어, SpEL이 expression.setValue(Object bean, Object value) 시도를 완료하기 위해 Short를 Long으로 강제 변환할 필요가 있을 때, core.convert 시스템이 그 강제 변환을 수행합니다.
이제 웹 또는 데스크톱 애플리케이션과 같은 일반적인 클라이언트 환경의 타입 변환 요구 사항을 고려해 보십시오. 이러한 환경에서는 일반적으로 클라이언트 포스트백 프로세스를 지원하기 위해 String으로 변환하고, 뷰 렌더링 프로세스를 지원하기 위해 다시 String으로 변환합니다. 또한, 종종 String 값들을 로컬라이즈해야 합니다.
보다 일반적인 core.convert Converter SPI는 이러한 포맷팅 요구 사항을 직접적으로 다루지 않습니다. 이를 직접적으로 다루기 위해, Spring은 클라이언트 환경에서 PropertyEditor 구현에 대한 간단하고 견고한 대안인 편리한 Formatter SPI를 제공합니다.
일반적으로, 범용 타입 변환 로직을 구현해야 할 때(예: java.util.Date와 Long 사이의 변환) Converter SPI를 사용할 수 있습니다. 웹 애플리케이션과 같은 클라이언트 환경에서 작업하며 로컬라이즈된 필드 값을 파싱하고 출력해야 할 때는 Formatter SPI를 사용할 수 있습니다. ConversionService는 두 SPI 모두에 대해 통합된 타입 변환 API를 제공합니다.
Formatter SPI필드 포맷팅 로직을 구현하기 위한 Formatter SPI는 단순하고 강하게 타입이 지정되어 있습니다. 다음 예시는 Formatter 인터페이스 정의를 보여 줍니다:
1package org.springframework.format; 2 3public interface Formatter<T> extends Printer<T>, Parser<T> { 4}
Formatter는 Printer 및 Parser 빌딩 블록 인터페이스로부터 확장됩니다. 다음 예시는 이 두 인터페이스의 정의를 보여 줍니다:
1public interface Printer<T> { 2 3 String print(T fieldValue, Locale locale); 4}
1import java.text.ParseException; 2 3public interface Parser<T> { 4 5 T parse(String clientValue, Locale locale) throws ParseException; 6}
자신만의 Formatter를 생성하려면, 앞에서 보여 준 Formatter 인터페이스를 구현하면 됩니다. T를 포맷하고자 하는 객체 타입(예: java.util.Date)으로 파라미터화하십시오. 클라이언트 로케일에서 디스플레이하기 위해 T의 인스턴스를 출력하는 print() 연산을 구현하십시오.
클라이언트 로케일에서 반환된 포맷된 표현으로부터 T의 인스턴스를 파싱하는 parse() 연산을 구현하십시오. 파싱 시도가 실패하면, Formatter는 ParseException 또는 IllegalArgumentException을 던져야 합니다. Formatter 구현이 스레드 세이프하도록 주의해서 작성하십시오.
format 서브패키지는 편의를 위해 여러 Formatter 구현을 제공합니다. number 패키지는 java.text.NumberFormat을 사용하는 Number 객체를 포맷하기 위한 NumberStyleFormatter, CurrencyStyleFormatter, PercentStyleFormatter를 제공합니다. datetime 패키지는 java.text.DateFormat으로 java.util.Date 객체를 포맷하기 위한 DateFormatter와, @DurationFormat.Style enum에 정의된 다양한 스타일로 Duration 객체를 포맷하기 위한 DurationFormatter를 제공합니다(자세한 내용은 Format Annotation API 참조).
다음 DateFormatter는 Formatter 구현 예시입니다:
1package org.springframework.format.datetime; 2 3public final class DateFormatter implements Formatter<Date> { 4 5 private String pattern; 6 7 public DateFormatter(String pattern) { 8 this.pattern = pattern; 9 } 10 11 public String print(Date date, Locale locale) { 12 if (date == null) { 13 return ""; 14 } 15 return getDateFormat(locale).format(date); 16 } 17 18 public Date parse(String formatted, Locale locale) throws ParseException { 19 if (formatted.length() == 0) { 20 return null; 21 } 22 return getDateFormat(locale).parse(formatted); 23 } 24 25 protected DateFormat getDateFormat(Locale locale) { 26 DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); 27 dateFormat.setLenient(false); 28 return dateFormat; 29 } 30}
1class DateFormatter(private val pattern: String) : Formatter<Date> { 2 3 override fun print(date: Date, locale: Locale) 4 = getDateFormat(locale).format(date) 5 6 @Throws(ParseException::class) 7 override fun parse(formatted: String, locale: Locale) 8 = getDateFormat(locale).parse(formatted) 9 10 protected fun getDateFormat(locale: Locale): DateFormat { 11 val dateFormat = SimpleDateFormat(this.pattern, locale) 12 dateFormat.isLenient = false 13 return dateFormat 14 } 15}
Spring team은 커뮤니티 주도의 Formatter 컨트리뷰션을 환영합니다. 기여하려면
GitHub Issues를 참조하십시오.
필드 포맷팅은 필드 타입 또는 애노테이션으로 구성할 수 있습니다. 애노테이션을 Formatter에 바인딩하려면, AnnotationFormatterFactory를 구현하십시오. 다음 예시는 AnnotationFormatterFactory 인터페이스 정의를 보여 줍니다:
1package org.springframework.format; 2 3public interface AnnotationFormatterFactory<A extends Annotation> { 4 5 Set<Class<?>> getFieldTypes(); 6 7 Printer<?> getPrinter(A annotation, Class<?> fieldType); 8 9 Parser<?> getParser(A annotation, Class<?> fieldType); 10}
구현을 생성하려면:
annotationType (예: org.springframework.format.annotation.DateTimeFormat)으로 A를 파라미터화합니다.getFieldTypes()가 애노테이션을 사용할 수 있는 필드 타입들을 반환하게 합니다.getPrinter()가 애노테이션이 적용된 필드의 값을 출력할 Printer를 반환하게 합니다.getParser()가 애노테이션이 적용된 필드의 clientValue를 파싱할 Parser를 반환하게 합니다.다음 예시 AnnotationFormatterFactory 구현은 @NumberFormat 애노테이션을 포매터에 바인딩하여 넘버 스타일 또는 패턴을 지정할 수 있도록 합니다:
1public final class NumberFormatAnnotationFormatterFactory 2 implements AnnotationFormatterFactory<NumberFormat> { 3 4 private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class, 5 Integer.class, Long.class, Float.class, Double.class, 6 BigDecimal.class, BigInteger.class); 7 8 public Set<Class<?>> getFieldTypes() { 9 return FIELD_TYPES; 10 } 11 12 public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { 13 return configureFormatterFrom(annotation, fieldType); 14 } 15 16 public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { 17 return configureFormatterFrom(annotation, fieldType); 18 } 19 20 private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) { 21 if (!annotation.pattern().isEmpty()) { 22 return new NumberStyleFormatter(annotation.pattern()); 23 } 24 // else 25 return switch(annotation.style()) { 26 case Style.PERCENT -> new PercentStyleFormatter(); 27 case Style.CURRENCY -> new CurrencyStyleFormatter(); 28 default -> new NumberStyleFormatter(); 29 }; 30 } 31}
1class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> { 2 3 override fun getFieldTypes(): Set<Class<*>> { 4 return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java) 5 } 6 7 override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> { 8 return configureFormatterFrom(annotation, fieldType) 9 } 10 11 override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> { 12 return configureFormatterFrom(annotation, fieldType) 13 } 14 15 private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> { 16 return if (annotation.pattern.isNotEmpty()) { 17 NumberStyleFormatter(annotation.pattern) 18 } else { 19 val style = annotation.style 20 when { 21 style === NumberFormat.Style.PERCENT -> PercentStyleFormatter() 22 style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter() 23 else -> NumberStyleFormatter() 24 } 25 } 26 } 27}
포맷팅을 트리거하려면, 다음 예시에서 보듯이 필드에 @NumberFormat을 애노테이션으로 지정할 수 있습니다:
1public class MyModel { 2 3 @NumberFormat(style=Style.CURRENCY) 4 private BigDecimal decimal; 5}
1class MyModel( 2 @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal 3)
이식 가능한 포맷 애노테이션 API가 org.springframework.format.annotation 패키지에 존재합니다. Double 및 Long과 같은 Number 필드를 포맷하기 위해 @NumberFormat을 사용할 수 있고, Duration 필드를 ISO-8601 및 단순화된 스타일로 포맷하기 위해 @DurationFormat을 사용할 수 있으며, java.util.Date, java.util.Calendar, Long(밀리초 타임스탬프용) 및 JSR-310 java.time 타입과 같은 필드를 포맷하기 위해 @DateTimeFormat을 사용할 수 있습니다.
다음 예시는 @DateTimeFormat을 사용하여 java.util.Date를 ISO date(yyyy-MM-dd)로 포맷하는 방법을 보여 줍니다:
1public class MyModel { 2 3 @DateTimeFormat(iso=ISO.DATE) 4 private Date date; 5}
1class MyModel( 2 @DateTimeFormat(iso=ISO.DATE) private val date: Date 3)
자세한 내용은
@DateTimeFormat,
@DurationFormat, 그리고
@NumberFormat의 javadoc을 참조하십시오.
스타일 기반 포맷팅 및 파싱은 Java 런타임에 따라 변경될 수 있는 로케일 센서티브 패턴에 의존합니다. 특히, date, time 또는 number 파싱 및 포맷팅에 의존하는 애플리케이션은 JDK 20 이상에서 실행할 때 동작의 호환되지 않는 변경 사항을 겪을 수 있습니다. ISO 표준화된 포맷 또는 자신이 제어하는 구체적인 패턴을 사용하면 date, time 및 number 값의 시스템 독립적 및 로케일 독립적 파싱 및 포맷팅을 신뢰성 있게 수행할 수 있습니다.
@DateTimeFormat의 경우, 폴백 패턴을 사용하는 것도 호환성 문제를 해결하는 데 도움이 될 수 있습니다. 자세한 내용은 Spring Framework wiki의 Date and Time Formatting with JDK 20 and higher 페이지를 참조하십시오.
FormatterRegistry SPIFormatterRegistry는 포매터와 컨버터를 등록하기 위한 SPI입니다. FormattingConversionService는 대부분의 환경에 적합한 FormatterRegistry 구현입니다. 예를 들어, FormattingConversionServiceFactoryBean을 사용하여 이 변형을 Spring bean으로 프로그래매틱 또는 선언적으로 구성할 수 있습니다. 이 구현은 ConversionService도 구현하므로, Spring의 DataBinder 및 Spring Expression Language (SpEL)에서 직접 사용하도록 구성할 수 있습니다.
다음 예시는 FormatterRegistry SPI를 보여 줍니다:
1package org.springframework.format; 2 3public interface FormatterRegistry extends ConverterRegistry { 4 5 void addPrinter(Printer<?> printer); 6 7 void addParser(Parser<?> parser); 8 9 void addFormatter(Formatter<?> formatter); 10 11 void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); 12 13 void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); 14 15 void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); 16}
앞의 예시에서 보듯이, 필드 타입 또는 애노테이션으로 포매터를 등록할 수 있습니다.
FormatterRegistry SPI를 사용하면 컨트롤러 전반에 걸쳐 이러한 구성을 중복하지 않고 포맷팅 규칙을 중앙에서 구성할 수 있습니다. 예를 들어, 모든 date 필드가 특정 방식으로 포맷되도록 강제하거나, 특정 애노테이션이 지정된 필드가 특정 방식으로 포맷되도록 강제할 수 있습니다. 공유 FormatterRegistry를 사용하면 이러한 규칙을 한 번 정의하면 되고, 포맷팅이 필요할 때마다 이 규칙이 적용됩니다.
FormatterRegistrar SPIFormatterRegistrar는 FormatterRegistry를 통해 포매터와 컨버터를 등록하기 위한 SPI입니다. 다음 예시는 그 인터페이스 정의를 보여 줍니다:
1package org.springframework.format; 2 3public interface FormatterRegistrar { 4 5 void registerFormatters(FormatterRegistry registry); 6}
FormatterRegistrar는 date 포맷팅과 같은 특정 포맷팅 카테고리에 대해 여러 관련 컨버터와 포매터를 등록할 때 유용합니다. 또한, 포매터가 자신의 <T>와 다른 특정 필드 타입 아래에 인덱스되어야 할 때나 Printer/Parser 페어를 등록해야 할 때와 같이 선언적 등록이 충분하지 않은 경우에도 유용할 수 있습니다. 다음 섹션에서는 컨버터 및 포매터 등록에 대한 더 많은 정보를 제공합니다.
Spring MVC 챕터의 Conversion and Formatting을 참조하십시오.
Spring Type Conversion
Configuring a Global Date and Time Format