인프런/스프링 MVC 2편

19) 스프링 타입 컨버터

backend dev 2023. 2. 12.

스프링 타입 컨버터

 

문자를 숫자로 변환하거나, 반대로 숫자를 문자로 변환해야 하는 것 처럼

 

애플리케이션을 개발하다 보면 타입을 변환해야 하는 경우가 상당히 많다.

 

다음 예를 보자.

 

HelloController - 문자 타입을 숫자 타입으로 변경

@RestController
public class HelloController {

    @GetMapping("hello-v1")
    public String helloV1(HttpServletRequest request) {
        String data = request.getParameter("data"); //파라미터값은 String 타입으로 넘어온다.

        Integer intValue = Integer.valueOf(request.getParameter("data")); // 숫자타입으로 받고싶으면 이렇게 형변환을 해야한다.

        System.out.println("intValue = " + intValue);
        return "ok";
    }
}

HTTP 요청 파라미터는 모두 문자로 처리된다.

 

따라서 요청 파라미터를 자바에서 다른 타입으로 변환해서 사용하고 싶으면

 

다음과 같이 숫자 타입으로 변환하는 과정을 거쳐야 한다.

Integer intValue = Integer.valueOf(request.getParameter("data")); // 숫자타입으로 받고싶으면 이렇게 형변환을 해야한다.

 

 

이번에는 스프링 MVC가 제공하는 @RequestParam 을 사용해보자.

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

잘동작한다.

 

앞서 보았듯이 HTTP 쿼리 스트링으로 전달하는 data=10 부분에서 10은 숫자 10이 아니라 문자 10이다.

 

스프링이 제공하는 @RequestParam 을 사용하면 이 문자 10을 Integer 타입의 숫자 10으로 편리하게 받을 수 있다

 

이것은 스프링이 중간에서 타입을 변환해주었기 때문이다.

 

이러한 예는 @ModelAttribute , @PathVariable 에서도 확인할 수 있다.

 

 

스프링의 타입 변환 적용 예

  • 스프링 MVC 요청 파라미터 @RequestParam , @ModelAttribute , @PathVariable

 

  • @Value 등으로 YML 정보 읽기

 

  • XML에 넣은 스프링 빈 정보를 변환

 

  • 뷰를 렌더링 할 때

 

 

스프링과 타입 변환

이렇게 타입을 변환해야 하는 경우는 상당히 많다.

 

개발자가 직접 하나하나 타입 변환을 해야 한다면, 생각만 해도 괴로울 것이다.

 

스프링이 중간에 타입 변환기를 사용해서 타입을 String --> Integer 로 변환해주었기 때문에

개발자는 편리하게 해당 타입을 바로 받을 수 있다.

 

앞에서는 문자를 숫자로 변경하는 예시를 들었지만,

반대로 숫자를 문자로 변경하는 것도 가능하고, Boolean 타입을 숫자로 변경하는 것도 가능하다.

(모든 타입 다 변환가능)

 

만약 개발자가 새로운 타입을 만들어서 변환하고 싶으면 어떻게 하면 될까?

(새로운 클래스를 만들어서 변환하고 싶다면?)

 

 

 

컨버터 인터페이스

S를 T로 바꿔준다

 

스프링은 확장 가능한 컨버터 인터페이스를 제공한다.

 

개발자는 스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록하면 된다.

 

이 컨버터 인터페이스는 모든 타입에 적용할 수 있다.

 

필요하면 X Y 타입으로 변환하는 컨버터 인터페이스를 만들고,

또 Y X 타입으로 변환하는 컨버터 인터페이스를 만들어서 등록하면 된다.

 

예를 들어서

문자로 "true" 가 오면 Boolean 타입으로 받고 싶으면

String  -->  Boolean 타입으로 변환되도록 컨버터 인터페이스를 만들어서 등록하고,

반대로 적용하고 싶으면 Boolean  -->  String 타입으로 변환되도록 컨버터를 추가로 만들어서 등록하면 된다.

 

 

Converter를 사용하자.

 

실제 코드를 통해서 타입 컨버터를 이해해보자.

 

 

타입 컨버터 - Converter

타입 컨버터를 어떻게 사용하는지 코드로 알아보자.

 

타입 컨버터를 사용하려면 org.springframework.core.convert.converter.Converter 인터페이스를 구현하면 된다.

package org.springframework.core.convert.converter;

 

주의

Converter 라는 이름의 인터페이스가 많으니 조심해야 한다.

org.springframework.core.convert.converter.Converter 를 사용해야 한다.

 

 

컨버터 인터페이스

 

 

먼저 가장 단순한 형태인 문자를 숫자로 바꾸는 타입 컨버터를 만들어보자

 

 

StringToIntegerConverter - 문자를 숫자로 변환하는 타입 컨버터

@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> { //  <변경전 타입, 변경후 타입> 을 입력해준다!

    @Override
    public Integer convert(String source) {
        log.info("convert source (변경될 데이터) = {}", source);
        return Integer.valueOf(source);
    }
}

String --> Integer 로 변환하기 때문에 소스가 String 이 된다.

 

이 문자(String)를 Integer.valueOf(source)를 사용해서 숫자로 변경한 다음에 변경된 숫자를 반환하면 된다

 

 

IntegerToStringConverter - 숫자를 문자로 변환하는 타입 컨버터

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

    @Override
    public String convert(Integer source) {
        log.info("convert source (변경될 데이터) = {}", source);
        return String.valueOf(source);
    }
}

이번에는 숫자를 문자로 변환하는 타입 컨버터이다.

 

앞의 컨버터와 반대의 일을 한다.

 

이번에는 숫자가 입력되기 때문에 소스가 Integer 가 된다.

String.valueOf(source) 를 사용해서 문자로 변경한 다음 변경된 문자를 반환하면 된다.

 

 

테스트 코드를 통해서 타입 컨버터가 어떻게 동작하는지 확인해보자.

 

 

ConverterTest - 타입 컨버터 테스트 코드

public class ConverterTest {


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

    @Test
    void integerToString() {
        IntegerToStringConverter converter = new IntegerToStringConverter();
        String result = converter.convert(10);
        Assertions.assertThat(result).isEqualTo("10");
    }

}

잘 동작한다.

사용자 정의 타입 컨버터

타입 컨버터 이해를 돕기 위해 조금 다른 컨버터를 준비해보았다.

 

 

127.0.0.1:8080 과 같은 IP, PORT를 입력하면 IpPort 객체로 변환하는 컨버터를 만들어보자.

IpPort

@Getter
@EqualsAndHashCode //이 어노테이션을 넣으면 모든 필드를 사용해서 equals() , hashcode() 를 생성해준다.
public class IpPort {

    private String ip;
    private int port;

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

롬복의 @EqualsAndHashCode 를 넣으면 모든 필드를 사용해서 equals() , hashcode() 를 생성한다.

 

따라서 모든 필드의 값이 같다면 a.equals(b) 의 결과가 참이 된다.

@EqualsAndHashCode

@EqualsAndHashCode를 사용하면 아래의 equals() 와 hashcode()가  자동으로 생성되는것이다.

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    IpPort ipPort = (IpPort) o;
    return port == ipPort.port && Objects.equals(ip, ipPort.ip);
}

@Override
public int hashCode() {
    return Objects.hash(ip, port);
}

 

 

 

 

StringToIpPortConverter - 컨버터

 

"127.0.0.1:8080"로 들어오면 ip와 port를 알아서 나눠서 IpPort 객체로 변환하는 컨버터를 만들어보자.

(이제 나중에 스프링에 해당 컨버터를 등록해놓으면 @RequestParam,@ModelAttribute 등 

타입컨버터를 사용해야할때 해당 컨버터가 동작할것이다. 

@RequestBody나 @ResponseBody같이 바디에서 들어오는 값을 변환시켜주는 메시지컨버터는 컨버젼서비스를 이용하는것이 아니다.

주의!!!

메시지 컨버터( HttpMessageConverter )에는 컨버전 서비스가 적용되지 않는다.

 

특히 객체를 JSON으로 변환할 때 메시지 컨버터를 사용하면서 이 부분을 많이 오해하는데,

 

HttpMessageConverter의 역할

HTTP 메시지 바디의 내용을 객체로 변환하거나 객체를 HTTP 메시지 바디에 입력하는 것이다.

 

예를 들어서 JSON을 객체로 변환하는 메시지 컨버터는 내부에서 Jackson 같은 라이브러리를 사용한다.

 

객체를 JSON으로 변환한다면 그 결과는 이 라이브러리에 달린 것이다.

 

따라서 JSON 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶으면

 

해당 라이브러리가 제공하는 설정을 통해서 포맷을 지정해야 한다.

 

결과적으로 이것은 컨버전 서비스와 전혀 관계가 없다

 

컨버전 서비스는 @RequestParam , @ModelAttribute , @PathVariable , 뷰 템플릿 등에서 사용할 수 있다.

 

HttpMessageConverter참고(https://keeeeeepgoing.tistory.com/183))

 

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

127.0.0.1:8080 같은 문자를 입력하면 IpPort 객체를 만들어 반환한다.

IpPortToStringConverter

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

    @Override
    public String convert(IpPort source) {
        log.info("convert source (변경될 데이터) = {}", source);
        return source.getIp() + ":" + source.getPort();
    }
}

IpPort 객체를 입력하면 127.0.0.1:8080 같은 문자를 반환한다

 

 

ConverterTest - IpPort 컨버터 테스트 추가

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

@Test
void ipPortToString() {
    IpPortToStringConverter converter = new IpPortToStringConverter();
    IpPort source = new IpPort("127.0.0.1", 8080);
    String result = converter.convert(source);

    Assertions.assertThat(result).isEqualTo("127.0.0.1:8080");
}
Assertions.assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));

 

여기서 isEqualTo를 할수 있는 이유는 

@Getter
@EqualsAndHashCode //이 어노테이션을 넣으면 모든 필드를 사용해서 equals() , hashcode() 를 생성해준다.
public class IpPort {

IpPort에다가 @EqualsAndHashCode를 붙여놨기 때문에

객체안의 값을 비교할수 있는 equals()가 존재하기 때문이다.

잘 동작한다.

 

 

 

타입 컨버터 인터페이스가 단순해서 이해하기 어렵지 않을 것이다.

 

그런데 이렇게 타입 컨버터를 하나하나 직접 사용하면, 개발자가 직접 컨버팅 하는 것과 큰 차이가 없다.

 

타입 컨버터를 등록하고 관리하면서 편리하게 변환 기능을 제공하는 역할을 하는 무언가가 필요하다.

 

 

참고

스프링은 용도에 따라 다양한 방식의 타입 컨버터를 제공한다

 

Converter     기본 타입 컨버터 

 

ConverterFactory     전체 클래스 계층 구조가 필요할 때 

 

GenericConverter     정교한 구현, 대상 필드의 애노테이션 정보 사용 가능 

 

ConditionalGenericConverter     특정 조건이 참인 경우에만 실행

 

    

자세한 내용은 공식 문서를 참고

(https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#core-convert)

 

 

스프링은 문자, 숫자, 불린, Enum등 일반적인 타입에 대한 대부분의 컨버터를 기본으로 제공한다.

 

IDE에서 Converter , ConverterFactory , GenericConverter 의 구현체를 찾아보면 수 많은 컨버터를 확인할 수 있다.

 


컨버전 서비스 - ConversionService

 

이렇게 타입 컨버터를 하나하나 직접 찾아서 타입 변환에 사용하는 것은 매우 불편하다.

 

그래서 스프링은 개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용할 수 있는 기능을 제공하는데,

 

이것이 바로 컨버전 서비스( ConversionService )이다.

 

 

ConversionService 인터페이스

public interface ConversionService {


   boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

   boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

   @Nullable
   <T> T convert(@Nullable Object source, Class<T> targetType);

   @Nullable
   Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}

컨버전 서비스 인터페이스는 단순히

컨버팅이 가능한가? 확인하는 기능과,

컨버팅 기능을 제공한다.

 

 

사용 예를 확인해보자.

 

ConversionServiceTest - 컨버전 서비스 테스트 코드

public class ConversionServiceTest {

    @Test
    void conversionService() {
        //컨버터 등록
        DefaultConversionService conversionService = new DefaultConversionService(); //ConversionService를 구현한 구현체중 하나

        //ConversionService의 구현체에 컨버터를 등록한다.
        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");

        IpPort result = conversionService.convert("127.0.0.1:8080", IpPort.class);
        assertThat(result).isEqualTo(new IpPort("127.0.0.1", 8080));

        String stringResult = conversionService.convert(new IpPort("127.0.0.1", 8080), String.class);
        assertThat(stringResult).isEqualTo("127.0.0.1:8080");

    }

}

DefaultConversionService 는 ConversionService 인터페이스를 구현했는데,

추가로 컨버터를 등록하는 기능도 제공한다.

 

ConversionService의 convert() 메소드 파라미터

결과확인

ConversionService의 convert() 호출시 알아서 Source와 targetType을 보고 해당 컨버터를 호출해준다.

 

이렇게 ConversionService를 이용해서 어딘가에서 컨버터들을 등록해놓고,

컨버터가 필요한곳에서 ConversionService를 주입받아 사용하면 될것이다.

 

등록과 사용 분리

컨버터를 등록할 때는 StringToIntegerConverter 같은 타입 컨버터를 명확하게 알아야 한다.

 

반면에 컨버터를 사용하는 입장에서는 타입 컨버터를 전혀 몰라도 된다.

 

타입 컨버터들은 모두 컨버전 서비스 내부에 숨어서 제공된다.

 

따라서 타입을 변환을 원하는 사용자는 컨버전 서비스 인터페이스에만 의존하면 된다.

 

물론 컨버전 서비스를 등록하는 부분과 사용하는 부분을 분리하고 의존관계 주입을 사용해야 한다.

(스프링에서는 WebMvcConfigurer를 implements하고 addFormatters()를 이용해서 컨버터를 등록할 수 있다. 그렇게하면 스프링은 기본으로 내부에서 사용하는 ConversionService에 컨버터를 등록시켜준다. 이렇게 등록되면

@RequestParam,@ModelAttribute등에서 자연스럽게 해당 컨버터가 사용된다.)

컨버전 서비스 사용

Integer value = conversionService.convert("10", Integer.class)

 

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

 

인터페이스 분리 원칙 : 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.

(클라이언트가 어떤 기능을 위해 인터페이스를 구현하려고하는데 그 안에 필수로 구현해야하는 메소드가 자신이 이용하지 않는 메소드라면 클라이언트는 자신이 이용하지않는 메서드에도 의존하게 되는것이다.)

 

DefaultConversionService 는 다음 두 인터페이스를 구현했다.

 

1. ConversionService : 컨버터 사용에 초점

 

2. ConverterRegistry : 컨버터 등록에 초점

 

이렇게 인터페이스를 분리하면

컨버터를 사용하는 클라이언트 컨버터를 등록하고 관리하는 클라이언트의 관심사를 명확하게 분리할 수 있다.

 

특히 컨버터를 사용하는 클라이언트는 ConversionService 만 의존하면 되므로,

컨버터를 어떻게 등록하고 관리하는지는 전혀 몰라도 된다

(컨버터를 사용하는 메소드들만 모아놓은 ConversionService의 메소드들만 구현해서 사용하면 되니까)

 

결과적으로 컨버터를 사용하는 클라이언트는 꼭 필요한 메서드만 알게된다.

이렇게 인터페이스를 분리하는 것을 ISP 라 한다.

(추가로 인터페이스 분리원칙을 지키고있을때 만약 인터페이스를 수정할 필요가 생긴다면 수정해야하는 부분이 적을것이다.)

(스프링을 사용하다보면 인터페이스가 사용하는 부분과 등록하는 부분이 잘 나뉘어져있는것을 확인할 수 있다.)

 

 

 

스프링은 내부에서 ConversionService를 사용해서 타입을 변환한다.

 

예를 들어서 앞서 살펴본 @RequestParam 같은 곳에서 이 기능을 사용해서 타입을 변환한다.

 

이제 컨버전 서비스를 스프링에 적용해보자.

 

 

스프링에 Converter 적용하기

우리가 위에서 만든 컨버터들을 스프링이 사용할 수 있게 해보자.

 

 

 

WebConfig - 컨버터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    // Ctrl + O를 이용해서 오버라이드할 메소드를 찾아볼 수 있다.
    @Override
    public void addFormatters(FormatterRegistry registry) { //컨버터 등록은 addFormatters()를 이용하면된다.
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
    }

}

 

스프링은 내부에서 ConversionService를 제공한다.

 

우리는 WebMvcConfigurer 가 제공하는 addFormatters() 를 사용해서 추가하고 싶은 컨버터를 등록하면 된다.

 

이렇게 하면 스프링은 내부에서 사용하는 ConversionService에 컨버터를 추가해준다.

 

 

등록한 컨버터가 잘 동작하는지 확인해보자.

 

HelloController - 기존 코드 사용

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

 

@Requestparam에서 

StringToIntegerConverter가 잘 동작한것을 확인할 수 있다.

 

컨버터를 등록해주니까 스프링이 알아서 컨버터를 찾아 사용하는것을 확인가능.

 

그런데 생각해보면 StringToIntegerConverter 를 등록하기 전에도 이 코드는 잘 수행되었다.

 

그것은 스프링이 내부에서 수 많은 기본 컨버터들을 제공하기 때문이다.

(일반적인건 다있음)

 

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

 

 

HelloController - ipPort객체로 테스트

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

@RequestParam이 우리가 만든 StringToIpPortConverter를 이용해서 ipPort라는 파라미터명으로 들어온

파라미터값을 IpPort객체로 변환시킬수 있을까?

 

(파라미터명을 생략하면 참조변수명을 파라미터명으로 사용한다)

(파라미터값은 항상 String으로 들어온다)

(물론 @ModelAttribute , @PathVariable에서도 잘 동작한다.)

 

사용

파라미터값은 항상 String이므로 원하는 객체에 대한 컨버터가 필요하다면

String -> 원하는 객체에 대한 컨버터를 등록해주면 될것이다.

 

위의 예시처럼 "127.0.0.1:8080"을 받아 IpPort 객체안의 필드에 잘 매핑되어 객체를 생성해주는것처럼

응용해서 사용할 수 있다.

 

 

결과 확인

등록된 컨버터를 이용해서 잘 변환한것을 확인할 수 있었다.

스프링이 컨트롤러가 호출되기전에 미리 컨버터를 이용해서 파라미터를 만들어놓고 

컨트롤러가 호출될때 만들어놓은 파라미터(객체)를 넣어준다.

https://keeeeeepgoing.tistory.com/183

 

 

 

처리 과정

@RequestParam@RequestParam을 처리하는

 

ArgumentResolverRequestParamMethodArgumentResolver에서

 

ConversionService를 사용해서 타입을 변환한다.

 

부모 클래스와 다양한 외부 클래스를 호출하는 등 복잡한 내부 과정을 거치기 때문에

 

대략 이렇게 처리되는 것으로 이해해도 충분하다.

 

만약 더 깊이있게 확인하고 싶으면 IpPortConverter에 디버그 브레이크 포인트를 걸어서 확인해보자

 


뷰 템플릿에 컨버터 적용하기

 

 

이번에는 뷰 템플릿에 컨버터를 적용하는 방법을 알아보자.

 

타임리프는 렌더링 시에 컨버터를 적용해서 렌더링 하는 방법을 편리하게 지원한다.

 

이전까지는 문자를 객체로 변환했다면, 이번에는 그 반대로 객체를 문자로 변환하는 작업을 확인할 수 있다.

(위에서 파라미터값을 ipPort객체로 바꾸는 것을 해봤다. 하지만 뷰템플릿은 객체를 문자로 바꿔서 렌더링해야하므로 객체를 문자로 바꾸는 작업을 해보자.)

 

 

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

converter-view.html의 일부

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

 

타임리프는 ${{...}} 를 사용하면 자동으로 컨버전 서비스를 사용해서 변환된 결과를 출력해준다.

(중괄호가 2개이다!)

 

물론 스프링과 통합 되어서 스프링이 제공하는 컨버전 서비스를 사용하므로, 우리가 등록한 컨버터들을 사용할 수 있다.

 

변수 표현식 : ${...}

${...}

컨버전 서비스 적용 : ${{...}}

${{...}}

 

실행 결과

첫번째 number는 컨버터를 적용하지않고 타임리프가 숫자를 문자로 자동으로 변환해서 보여준 모습

두번쨰 number는 컨버터를 적용해서 IntegerToStringConverter가 동작한 모습

 

첫번째 ipPort는 컨버터를 적용하지않아서 객체의 toString()이 실행된 값이 보이는 모습

두번째 ipPort는 IpPortToStringConverter가 동작해서 String으로 변환된값이 보이는 모습

 

 

${{number}}

뷰 템플릿은 데이터를 문자로 출력한다.

따라서 컨버터를 적용하게 되면

Integer 타입인 10000 을 String 타입으로 변환하는 컨버터인 IntegerToStringConverter 를 실행하게 된다.

 

이 부분은 컨버터를 실행하지 않아도

타임리프가 숫자를 문자로 자동으로 변환하기 때문에 컨버터를 적용할 때와 하지 않을 때가 같다.

 

${{ipPort}}

뷰 템플릿은 데이터를 문자로 출력한다.

따라서 컨버터를 적용하게 되면

IpPort 타입을 String 타입으로 변환해야 하므로 IpPortToStringConverte 가 적용된다.

그 결과 127.0.0.1:8080 가 출력된다.

 

 

 

폼에 적용하기

이번에는 컨버터를 폼에 적용해보자

 

 

컨트롤러에 코드 추가

@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 객체를 데이터를 전달하는 DTO로 사용한다.

 

@GetMapping("/converter/edit")

IpPort 를 뷰 템플릿 폼에 출력한다

 

@PostMapping("/converter/edit")

뷰 템플릿 폼의 IpPort 정보를 받아서 출력한다.

 

 

converter-form.html의 일부분

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

th:object를 이용해서 form 객체를 커맨드 객체로 등록하고

커맨드 객체의 필드를 사용하기 위해 *{}문법을 이용한다.

th:filed ="*{ipPort}" 은 ipPort객체가 String으로 컨버팅이 필요한 부분인데

th:filed는 알아서 컨버팅이 동작한다.

 

타임리프의 th:field 는 앞서 설명했듯이 id , name 를 출력하는 등 다양한 기능이 있는데,

여기에 컨버전 서비스도 함께 적용된다.

th:filed는 컨버팅이 적용된모습, th:value는 컨버팅이 동작하지 않은 모습

 

th:field 가 자동으로 컨버전 서비스를 적용해주어서 ${{ipPort}} 처럼 적용이 되었다.

따라서 IpPort --> String 으로 변환된다.

 

 

위의 뷰에서 제출하기를 누르면

 

@ModelAttribute로 인해 문자인 "127.0.0.1:8080"을 StringToIpPortConverter를 이용해서 Form객체로 변환해주고 

 

뷰에서 ${{ipPort}} 로인해  IpPortToStringConverter가 동작한다. 

'인프런 > 스프링 MVC 2편' 카테고리의 다른 글

끝) 파일업로드  (2) 2023.02.13
20) Formatter(포맷터)  (0) 2023.02.12
18) API 예외처리 (3)  (0) 2023.02.12
17) API 예외 처리(2)  (0) 2023.02.10
16) API 예외 처리  (0) 2023.02.09

댓글