Spring

[Spring] Converter와 Formatter

정 효 2024. 7. 8. 21:47

Converter

  • 객체와 객체간의 타입 변환 (범용)
  • 직접 타입 변환 시
@RestController  
public class HelloController {  

    @GetMapping("/hello-v1")  
    public String helloV1(HttpServletRequest request) {  
        String data = request.getParameter("data");  
        Integer intValue = Integer.valueOf(data);  
        System.out.println("intValue = " + intValue);  
        return "ok";  
    }
}

 

 

  • Spring이 제공하는 ConversionService 이용
@GetMapping("/hello-v2")  
public String helloV2(@RequestParam Integer data) {  
    System.out.println("data = " + data);  
    return "ok";  
}

-> @ModelAttribute@PathVariable에도 적용됨

 

 

  • Converter 인터페이스를 구현하여 직접 Converter 생성 가능
package org.springframework.core.convert.converter;

@FunctionalInterface  
public interface Converter<S, T> {
    @Nullable  
    T convert(S source);
}

 

 

  • Integer to String Converter
@Slf4j  
public class IntegerToStringConverter implements Converter<Integer, String> {  

    @Override  
    public String convert(Integer source) {  
        log.info("convert source={}", source);  
        return String.valueOf(source);  
    }  
}

 

 

  • Strign to Integer Converter
@Slf4j  
public class StringToIntegerConverter implements Converter<String, Integer> {  

    @Override  
    public Integer convert(String source) {  
        log.info("convert source={}", source);  
        return Integer.valueOf(source);  
    }  
}

 

 

  • IpPort
@Getter  
@AllArgsConstructor  
@EqualsAndHashCode  
public class IpPort {  
    private String ip;  
    private int port;  
}

 

 

  • IpPort to String Converter
@Slf4j  
public class IpPortToStringConverter implements Converter<IpPort, String> {  

    @Override  
    public String convert(IpPort source) {  
        log.info("converter source={}", source);  
        return source.getIp() + ":" + source.getPort();  
    }  
}

 

 

  • String to IpPort Converter
@Slf4j  
public class StringToIpPortConverter implements Converter<String, IpPort> {  

    @Override  
    public IpPort convert(String source) {  
        log.info("convert source={}", source);  
        //"127.0.0.1:8080"  
        String[] split = source.split(":");  
        String ip = split[0];  
        int port = Integer.parseInt(split[1]);  
        return new IpPort(ip, port);  
    }  
}

 

 

  • 직접 Converter 호출
public class ConverterTest {  

    @Test  
    void stringToInteger() {
        StringToIntegerConverter converter = new StringToIntegerConverter();  
        Integer result = converter.convert("10");

        assertThat(result).isEqualTo(10);  
    }

    @Test  
    void integerToString() {  
        IntegerToStringConverter converter = new IntegerToStringConverter();  
        String result = converter.convert(10);  

        assertThat(result).isEqualTo("10");  
    }
}

 

 

  • ConversionService 등록하여 사용
@Test  
void conversionService() {  
    //등록  
    DefaultConversionService conversionService = new DefaultConversionService();  
    conversionService.addConverter(new StringToIntegerConverter());
    conversionService.addConverter(new IntegerToStringConverter());

    assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
    assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
}

 

 

  • Spring에 등록하여 사용
@Configuration  
public class WebConfig implements WebMvcConfigurer {  

    @Override  
    public void addFormatters(FormatterRegistry registry) {  
        registry.addConverter(new StringToIntegerConverter());  
        registry.addConverter(new IntegerToStringConverter());
        registry.addConverter(new StringToIpPortConverter());  
        registry.addConverter(new IpPortToStringConverter());
    }
}
- @RequestParam, @ModelAttribute, @PathVariable 등에 자동 적용

 

 

  • Thymeleaf에서 사용
@Slf4j  
@Controller  
public class ConverterController {  

    @GetMapping("/converter-view")  
    public String converterView(Model model) {  
        model.addAttribute("number", 10000);  
        model.addAttribute("ipPort", new IpPort("127.0.0.1", 8080));  
        return "converter-view";  
    }  

    @GetMapping("/converter/edit")  
    public String converterForm(Model model) {  
        IpPort ipPort = new IpPort("127.0.0.1", 8080);  
        Form form = new Form(ipPort);  
        model.addAttribute("form", form);  
        return "converter-form";  
    }  

    @PostMapping("/converter/edit")  
    public String converterEdit(@ModelAttribute Form form, Model model) {  
        IpPort ipPort = form.getIpPort();  
        model.addAttribute("ipPort", ipPort);  
        return "converter-view";  
    }  

    @Data  
    static class Form {  
        private IpPort ipPort;  

        public Form(IpPort ipPort) {  
            this.ipPort = ipPort;  
        }  
    }  
}

 

 

converter-form.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<form th:object="${form}" th:method="post">  
    th:field <input type="text" th:field="*{ipPort}"><br/>  
    th:value <input type="text" th:value="*{ipPort}">(보여주기 용도)<br/>    
    <input type="submit"/>  
</form>  
</body>  
</html>
- th:field 사용 시 Converter 자동 적용

 

 

 

converter-view.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<ul>  
    <li>${number}: <span th:text="${number}" ></span></li>  
    <li>${{number}}: <span th:text="${{number}}" ></span></li>  
    <li>${ipPort}: <span th:text="${ipPort}" ></span></li>  
    <li>${{ipPort}}: <span th:text="${{ipPort}}" ></span></li>  
</ul>  
</body>  
</html>
- ${{}} 안에 사용 시 Converter 적용

 

 

 

 

 

Formatter

  • 객체와 문자간의 변환 + 현지화 기능
  • Converter의 특별한 버전
  • Formatter 인터페이스 구현하여 사용

 

package org.springframework.format;

@FunctionalInterface  
public interface Printer<T> {  
    String print(T object, Locale locale);
}

@FunctionalInterface  
public interface Parser<T> {
    T parse(String text, Locale locale) throws ParseException;
}

public interface Formatter<T> extends Printer<T>, Parser<T> {  

}

 

 

@Slf4j  
public class MyNumberFormatter implements Formatter<Number> {  

    @Override  
    public Number parse(String text, Locale locale) throws ParseException {  
        log.info("text={}, locale={}", text, locale);  
        //"1,000" -> 1000  
        NumberFormat format = NumberFormat.getInstance(locale);  
        return format.parse(text);  
    }  

    @Override  
    public String print(Number object, Locale locale) {  
        log.info("object={}, locale={}", object, locale);  
        return NumberFormat.getInstance(locale).format(object);  
    }  
}

 

 

  • 직접 호출하여 사용
MyNumberFormatter formatter = new MyNumberFormatter();  

@Test  
void parse() throws ParseException {  
    Number result = formatter.parse("1,000", Locale.KOREA);  
    assertThat(result).isEqualTo(1000L);  
}  

@Test  
void print() {  
    String result = formatter.print(1000, Locale.KOREA);  
    assertThat(result).isEqualTo("1,000");  
}

 

 

  • FormattingConversionService 등록하여 사용
@Test  
void formattingConversionService() {  
    DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();  =
    conversionService.addFormatter(new MyNumberFormatter());  

    //포맷터 사용  
    assertThat(conversionService.convert(1000, String.class)).isEqualTo("1,000");  
    assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);  
}

 

 

  • Spring에 등록
@Configuration  
public class WebConfig implements WebMvcConfigurer {  

    @Override  
    public void addFormatters(FormatterRegistry registry) {  
        registry.addFormatter(new MyNumberFormatter());  
    }  
}

 

 

  • Spring이 제공하는 annotation 기반 Formatter
@Controller  
public class FormatterController {  

    @GetMapping("/formatter/edit")  
    public String formatterForm(Model model) {  
        Form form = new Form();  
        form.setNumber(10000);  
        form.setLocalDateTime(LocalDateTime.now());  
        model.addAttribute("form", form);  
        return "formatter-form";  
    }  

    @PostMapping("/formatter/edit")  
    public String formatterEdit(@ModelAttribute Form form) {  
        return "formatter-view";  
    }  

    @Data  
    static class Form {  
        @NumberFormat(pattern = "###,###")  
        private Integer number;  

        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
        private LocalDateTime localDateTime;  
    }  
}
- @NumberFormat: 문자 <-> 숫자 변환
- @DateTimeFormat: 문자 <-> 날짜 변환

 

 

formatter-form.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<form th:object="${form}" th:method="post">  
    number <input type="text" th:field="*{number}"><br/>  
    localDateTime <input type="text" th:field="*{localDateTime}"><br/>  
    <input type="submit"/>  
</form>  
</body>  
</html>

 

 

 

formatter-view.html

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<ul>  
    <li>${form.number}: <span th:text="${form.number}"></span></li>  
    <li>${{form.number}}: <span th:text="${{form.number}}"></span></li>  
    <li>${form.localDateTime}: <span th:text="${form.localDateTime}"></span></li>  
    <li>${{form.localDateTime}}: <span th:text="${{form.localDateTime}}"></span>  
    </li></ul>  
</body>  
</html>

 

 

 

 - 중괄호 하나일 땐 `Formatter`에 설정된 패턴을 확인하여 Integer, LocalDateTime에 맞게 변환하여 출력
 - 두개일 땐 thymeleaf에서 `Formatter` 적용하여 지정한 패턴으로 출력