Spring

[인프런/스프링 MVC 2편] 10. 스프링 타입 컨버터

주니어주니 2023. 5. 31. 20:17

 

 

1. 스프링 타입 컨버터

 

1-1. 스프링의 자동 타입 변환 

 

1) 스프링의 타입 변환 적용 예시

  • 스프링 MVC 요청 파라미터 (@RequestParam, @ModelAttribute, @PathVariable)
  • @Value 등으로 YML 정보 읽기 
  • XML에 넣은 스프링 빈 정보 변환
  • 뷰 렌더링 할 때

 

2) 스프링 MVC 요청 파라미터 타입 변환 예시

  • HTTP 요청 파라미터는 모두 문자로 들어옴

 

(1) @RequestParam

원래 

-> 문자로 받아서 -> Integer로 타입 변환 (수동)

 @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";
 }

 

스프링 MVC가 제공하는 @RequestParam 사용 

-> 문자를 Integer로 변환 (자동)

@GetMapping("/hello-v2")
public String helloV2(@RequestParam Integer data) {
    System.out.println("data = " + data);
    return "ok";
}

 

 

(2) @ModelAttribute 

@ModelAttribute UserData data

class UserData {
	Integer data;
}

 

 

(3) @PathVariable

/users/{userId}
@PathVariable("userId") Integer data

 

 


 

1-2. 타입 컨버터 - Converter 직접 사용하기

 

타입 컨버터 사용법 : 컨버터 인터페이스 구현 (org.springframework.core.convert.converter.Converter)

 

 

1) 컨버터 인터페이스 

package org.springframework.core.convert.converter;

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

 

 

2) 문자 -> 숫자 변환 (StringToIntegerConverter)

  • String을 source로 받아서
  • Integer.valueOf(source) -> 숫자로 변환
package hello.typeconverter.converter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

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

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

 

 

3) 숫자 -> 문자 변환 (IntegerToStringConverter)

  • Integer를 source로 받아서
  • String.valueOf(source) -> 문자로 변환
package hello.typeconverter.converter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

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

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

 

 

--------- 사용자 정의 타입 컨버터 ---------

 

4) (사용자 정의 컨버터) IpPort 객체 생성 - Ip, port 입력하면 IpPort 객체로 변환

  • @EqualsAndHashCode
    • 모든 필드를 사용해서 equals(), hashcode() 생성
    • 모든 필드의 값이 같으면 a.equals(b)의 결과가 참
    • 객체의 참조값이 달라도 값이 같으면 같은 객체로 판단 (new IpPort 객체를 equals로 비교할 때)
package hello.typeconverter.type;

import lombok.EqualsAndHashCode;
import lombok.Getter;

@Getter
@EqualsAndHashCode
public class IpPort {

    private String ip;
    private int port;

    public IpPort(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }
}

 

 

5) (사용자 정의 컨버터) 문자 -> IpPort 객체 변환 

  • "127.0.0.1:8080" 문자 -> IpPort("127.0.0.1", 8080) 객체 반환
package hello.typeconverter.converter;

import hello.typeconverter.type.IpPort;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

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

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

 

 

 

6) (사용자 정의 컨버터) IpPort 객체 -> 문자 변환

  • IpPort 객체 -> "127.0.0.1:8080" 반환
package hello.typeconverter.converter;

import hello.typeconverter.type.IpPort;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;

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

    @Override
    public String convert(IpPort source) {
        log.info("convert source={}", source);
        // IpPort 객체 -> "127.0.0.1:8080"
        return source.getIp() + ":" + source.getPort();
    }
}

 

 

 

7) 타입 컨버터 동작 테스트

package hello.typeconverter.converter;

import hello.typeconverter.type.IpPort;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

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");
    }

    @Test
    void stringToIpPort() {
        StringToIpPortConverter converter = new StringToIpPortConverter();
        IpPort result = converter.convert("127.0.0.1:8080");
        assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));
    }

    @Test
    void IpPortToString() {
        IpPortToStringConverter converter = new IpPortToStringConverter();
        String result = converter.convert(new IpPort("127.0.0.1", 8080));
        assertThat(result).isEqualTo("127.0.0.1:8080");
    }
}
  • -> 타입 컨버터 직접 new로 생성하고, conver()로 하나하나 직접 사용

 

📌 참고
valueOf()  : Integer 타입으로 반환
parseInt() : int 타입으로 반환

 


 

 

1-3. 컨버전 서비스 - ConversionService 

 

ConversionService

: 타입 컨버터를 하나하나 직접 찾아서 타입 변환에 사용 X 

개별 컨버터를 한 곳에 모아두고 편리하게 사용

 

 

1) ConversionService 인터페이스 

  • canConvert() : 컨버팅이 가능한지 확인
  • convert() : 컨버팅 기능
package org.springframework.core.convert;

import org.springframework.lang.Nullable;

public interface ConversionService {

    boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
    
    <T> T convert(@Nullable Object source, Class<T> targetType);
    Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}

 

 

 

2) 컨버전 서비스 테스트 (사용방법 테스트)

package hello.typeconverter.converter;

import hello.typeconverter.type.IpPort;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.support.DefaultConversionService;

import static org.assertj.core.api.Assertions.*;

public class ConversionServiceTest {

    @Test
    void conversionService() {
        // 등록
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToIntegerConverter());
        conversionService.addConverter(new IntegerToStringConverter());
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());

        // 사용
        assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
        assertThat(conversionService.convert(10, String.class)).isEqualTo("10");
        assertThat(conversionService.convert("127.0.0.1:8080", IpPort.class)).isEqualTo(new IpPort("127.0.0.1", 8080));
        assertThat(conversionService.convert(new IpPort("127.0.0.1", 8080), String.class)).isEqualTo("127.0.0.1:8080");
    }
}

 

(1) DefaultConversionService 

  • ConversionService 인터페이스 구현

  • 컨버터 등록
    • addConverter(new 컨버터)
    • 등록할 때는 타입 컨버터를 명확하게 알아야 함

  • 컨버터 사용
    • Integer value = conversionService.convert("10", Integer.class)
    • 타입 컨버터를 몰라도 그냥 사용 O

 

 

📌 인터페이스 분리 원칙 - ISP (Interface Segregation Principle)

DefaultConversionService는 두 인터페이스를 구현함
- ConversionService : 컨버터 사용에 초점
- ConvertRegistry : 컨버터 등록에 초점

-> 인터페이스 분리 -> 이용하지 않는 메소드에는 의존 X

 

 


 

1-4. 스프링에 Converter 적용하기 

 

스프링은 내부에서 ConversionService 제공 -> WebConfig에 컨버터 등록해서 사용

 

 

1) 컨버터 등록 - WebConfig

  • WebMvcConfigurer가 제공하는 addFormatter() 사용해서 컨버터 등록
    -> 스프링은 내부에서 사용하는 ConversionService에 컨버터 추가
package hello.typeconverter;

import hello.typeconverter.converter.IntegerToStringConverter;
import hello.typeconverter.converter.IpPortToStringConverter;
import hello.typeconverter.converter.StringToIntegerConverter;
import hello.typeconverter.converter.StringToIpPortConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@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());
    }
}

 

 

2) 컨버터 동작 확인 

 

(1) String -> Integer 컨버터 동작 확인

@RestController
public class HelloController {

    @GetMapping("/hello-v2")
    public String helloV2(@RequestParam Integer data) {
        System.out.println("data = " + data);
        return "ok";
    }

 

 

-> StringToIntegerConverter 작동하는 로그 확인

-> 이런 컨버터는 스프링이 내부에서 수많은 기본 컨버터 제공하기 때문에 컨버터 추가하지 않아도 기본 수행

-> 컨버터를 추가하면 추가한 컨버터가 기본 컨버터보다 높은 우선순위

 

 

 

(2) String -> IpPort 컨버터 동작 확인 (사용자 정의 타입 컨버터)

@RestController
public class HelloController {

    @GetMapping("/ip-port")
    public String ipPort(@RequestParam IpPort ipPort) {
        System.out.println("ipPort = " + ipPort.getIp());
        System.out.println("ipPort = " + ipPort.getPort());
        return "ok";
    }

 

 

-> StringToIpPortConverter 작동 로그 확인

 

 

 

3) 처리 과정 

  • @RequestParam
    • @RequestParam을 처리하는 ArgumentResolver인 RequestParamMethodArgumentResolver
      -> 내부에서 ConversionService를 사용해서 타입 반환

 

📌 ArgumentResolver 의 타입 변환

1) 컨트롤러 파라미터가 @RequestBody, HttpEntity (직접 적는 데이터) 인 경우
-> HttpMessageConverter 사용 -> 타입 변환

* HttpMessageConverter의 역할 : HTTP 메시지 바디의 내용 <-> 객체 변환
( JSON <-> 객체 변환할 때 숫자나 날짜 포맷 변경 : 해당 라이브러리(Jackson)가 제공하는 설정으로 포맷 지정


2) 컨트롤러 파라미터가 @ModelAttribute, @PathVariable, @RequestParam / 뷰 템플릿 인 경우
-> ConversionService 사용 -> 타입 변환

 

 

 


1-5. 뷰 템플릿에 컨버터 적용하기 

 

* 뷰 템플릿은 데이터를 문자로 출력

 

 

1) 숫자, 객체 -> 문자 

 

(1) 컨트롤러 - ConverterController

@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";
    }

 

 

(2) 뷰 - 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>
  • 변수 표현식 : ${ ... }
  • 컨버전 서비스 적용 : ${{ ... }}

 

 

(3) 실행 

 

  • ${{number}}
    • IntegerToStringConverter 적용 : 숫자 -> 문자
    • 이건 컨버터를 실행하지 않아도 타임리프가 자동으로 숫자 -> 문자로 변환해줌

  • ${{ipPort}}
    • IpPortToStringConverter 적용 : IpPort 객체 -> 문자
    • 컨버터 적용 X : 객체를 그대로 출력 (toString())

 

 

2) Form에 적용 - 문자 <-> 객체 

 

(1) 컨트롤러 - ConverterController

@Controller
public class ConverterController {

    @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;
        }
    }
}
  • Form 객체 : ipPort 객체 자체를 담아서 뷰 템플릿으로 전달하는 용도

  • GET /convert/edit : IpPort를 Form에 담아서 뷰 템플릿 폼에 출력
  • POST /convert/edit : 뷰 템플릿 폼의 IpPort 정보를 받아서 출력

 

 

(2) 뷰 템플릿 - 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 : 컨버전 서비스 기능도 있음 ;;
  • th:value : 컨버전 적용 X 

 

 

(3) 실행

 

  • GET /converter/edit
    • th:field : 자동으로 컨버전 서비스 적용 - 객체 -> 문자 ( IpPortToStringConverter )
    • IpPort 객체 -> String 변환

 

 

 

  • POST /converter/edit
    • @ModelAttribute -> 뷰에 출력된 데이터(String)를 객체(IpPort)로 변환 ( StringToPiPortConverter )
    • String -> IpPort 변환
    • ${{ipPort}} -> 객체 정보를 다시 뷰 템플릿에 출력 - 객체 -> 문자 ( IpPortToStringConverter )

 

 

 


 

2. 포맷터 - Formatter 

 

2-1. 포맷터

 

  • Converter : 범용 (아무 객체 <-> 아무 객체)
  • Formatter : 문자에 특화 ( 객체 <-> 문자 ) + 현지화(Locale)
    • Converter의 특별한 버전
    • 숫자 1000 <-> 문자 "1,000"
    • 날짜 객체 <-> 문자 "2021-01-01 10:50:11"

 

1) Formatter 인터페이스 

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

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

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

 

 

2) 포맷터 - MyNumberFormatter 

package hello.typeconverter.formatter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.format.Formatter;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

@Slf4j
public class MyNumberFormatter implements Formatter<Number> {

    @Override
    public Number parse(String text, Locale locale) throws ParseException {
        log.info("text={}, locle={}", 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={}, local={}", object, locale);
        // 1000 -> "1,000"
        NumberFormat instance = NumberFormat.getInstance(locale);
        return instance.format(object);
    }
}
  • 자바가 기본으로 제공하는 NumberFormat 객체 : Locale 정보 활용해서 나라별로 다른 숫자 포맷 제공

  • Number : Integer, Long 등의 숫자타입의 부모 클래스

  • parse() : 문자 -> 숫자
  • print() : 숫자 객체 -> 문자

 

 

3) 동작 테스트

package hello.typeconverter.formatter;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import java.text.ParseException;
import java.util.Locale;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MyNumberFormatterTest {

    MyNumberFormatter formatter = new MyNumberFormatter();

    @Test
    void parse() throws ParseException {
        Number result = formatter.parse("1,000", Locale.KOREA);
        assertThat(result).isEqualTo(1000L);    // Long 타입 주의 (parse의 결과가 Long)
    }

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

 

 


 

2-2. 포맷터를 지원하는 컨버전 서비스 

 

원래 컨버전 서비스에는 컨버터만 등록 O, 포맷터 등록 X

근데 포맷터도 컨버터의 일종임

 

FormattingConversionService (포맷터를 지원하는 컨버전 서비스) -> 컨버전 서비스에 포맷터 추가 가능

-> 내부에서 어댑터 패턴을 사용해서 Formatter가 Converter처럼 동작하도록 지원

 

* DefaultFormattingConversionService : FormattingConversionService에 몇가지 기본 포맷터 추가 제공

 

 

1) 동작 테스트 

package hello.typeconverter.formatter;

import hello.typeconverter.converter.IpPortToStringConverter;
import hello.typeconverter.converter.StringToIpPortConverter;
import hello.typeconverter.type.IpPort;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.format.support.DefaultFormattingConversionService;

import static org.assertj.core.api.Assertions.*;

public class FormattingConversionServiceTest {

    @Test
    void formattingConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

        // 컨버터 등록
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());
        // 포맷터 등록
        conversionService.addFormatter(new MyNumberFormatter());

        // 컨버터 사용
        IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
        assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
        // 포맷터 사용
        String string = conversionService.convert(1000, String.class);
        assertThat(string).isEqualTo("1,000");
        Long number = conversionService.convert("1,000", Long.class);
        assertThat(number).isEqualTo(1000L);
    }
}

 

  • DefaultFormattiongConversionService
    • 컨버전 서비슨데 포맷터도 등록 가능
    • 컨버터처럼 똑같이 사용 ( convert() ) -> 알아서 적절한 parse, print 메소드 호출

 

 


 

2-3. 포맷터 적용 

 

1) 포맷터 등록 - WebConfig

package hello.typeconverter;

import hello.typeconverter.converter.IntegerToStringConverter;
import hello.typeconverter.converter.IpPortToStringConverter;
import hello.typeconverter.converter.StringToIntegerConverter;
import hello.typeconverter.converter.StringToIpPortConverter;
import hello.typeconverter.formatter.MyNumberFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@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());

        // 포맷터 추가
        registry.addFormatter(new MyNumberFormatter());
    }
}
  • StringToIntegerConverter(), IntegerToStringConverter() 와 MyNumberFormatter()는 모두 숫자 <-> 문자 변환
    -> 기능 겹침 -> 컨버터가 우선순위 -> 포맷터 적용 X, 컨버터 적용 O --> 주석처리 

 

 

2) 실행

 

(1) 객체 -> 문자

  • ${number} : MyNumberFormatter 적용 X -> Integer 객체 출력
  • ${{number}} : MyNumberFormatter 적용 O -> 문자 출력

 

 

 

(2) 문자 -> 객체

  • 문자 "10,000" -> Integer 객체 10000 로 변환

 

 


 

2-4. 스프링이 제공하는 기본 포맷터 

 

포맷터 -> 기본 형식 지정 -> 다른 형식으로 포맷 지정하기 어려움

 

  • 스프링이 제공하는 기본 포맷터
    • @NumberFormat : 숫자 관련 형식 지정 포맷터 사용 (NumberFormatAnnotationFormatterFactory)
    • @DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용 (Jsr310DateTimeFormatAnnotationFormatterFactory)

 

 

1) 컨트롤러 

package hello.typeconverter.controller;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.time.LocalDateTime;
import java.util.Date;

@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;
    }
}

 

 

2) 뷰 템플릿 

 

* 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>

 

 

3) 실행 

 

(1) GET /formatter/edit

  • th:field : Integer, LocaleDateTIme 객체 -> 문자 변환해서 뷰에 출력 ( @NumberFormat(pattern = "") 에서 지정한 형식 )

 

 

(2) POST /formatter/edit

  • @ModelAttribute : 뷰의 문자 -> Integer, LocaleDateTime 객체로 변환해서 객체에 담음
  • ${{ ... }} : Integer, LocaleDateTiem 객체 -> 문자 변환해서 뷰에 출력

 

 

📌 컨버터, 포맷터 사용법

1. 기본 제공 컨버터 말고 사용자 정의 컨버터나 포맷터 : WebConfig에 등록

2. 요청 파라미터에 적용 : @RequestParam, @ModelAttribute, @PathVariable

3. 뷰 템플릿에 적용
    - 일반 : ${{...}}
    - form에서 적용 : th:field