본문 바로가기
스프링/스프링 웹 개발 활용

[Spring] Converter 적용하기 (스프링과 뷰 템플릿에 적용)

by drCode 2024. 1. 7.
728x90
반응형

스프링에 Converter 적용하기 

 

웹 애플리케이션에 Converter 를 적용해보자.

 

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 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) {
        // 없어도 잘 동작하는데, 그 이유는 기본적으로 스프링이 제공하기 떄문이다.
        // 사용자정의 컨버터는 직접 구현해줘야함. IP 같은 것
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());

    }
}

 

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

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

 

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

 

등록한 컨버터가 잘 동작하는지 확인하기 위하여 HelloController의 /hello-v2 를 이용해본다.

 

HelloController - 기존 코드

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

 

실행  -  http://localhost:8080/hello-v2?data=10

http://localhost:8080/hello-v2?data=10

 

실행 로그

StringToIntegerConverter

StringToIntegerConverter : convert source=10

data = 10

 

?data=10 의 쿼리 파라미터는 문자이고 이것을 Integer data로 변환하는 과정이 필요하다.

실행해보면 직접 등록한 StringToIntegerConverter가 작동하는 로그를 확인할 수 있다.

 

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

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

 

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

 

이번에는 직접 정의한 타입인 IpPort를 사용해보자.

 

HelloController - 추가

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

 

실행 : http://localhost:8080/ip-port?ipPort=127.0.0.1:8080

실행 로그

 

StringToIpPortConverter : convert source=127.0.0.1:8080

ipPort IP = 127.0.0.1

ipPort PORT = 8080

 

?ipPort=127.0.0.1:8080 쿼리 스트링이

@RequestParam IpPort ipPort 에서 객체 타입으로 잘 변환된 것을 확인할 수 있다.

 

처리 과정

@RequestParam@RequestParam을 처리하는 ArgumentResolver

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

부모 클래스와 다양한 외부 클래스를 호출하는 등 복잡한 내부 과정을 거친다.

자세한 과정은 IpPortConverter에 디버그 브레이크 포인트를 걸어서 확인해보면 된다.

 

브레이크포인트

 

 

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

 

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

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

 

ConverterController

package hello.typeconverter.controller;

import hello.typeconverter.type.IpPort;
import lombok.Data;
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;

@Controller
public class ConvertorController {

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

 

Model에 숫자 10000 와 ipPort 객체를 담아서 뷰 템플릿에 전달한다.

 

resources/templates/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>

 

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

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

 

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

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

 

실행 : http://localhost:8080/converter-view

실행 결과 로그

IntegerToStringConverter : convert source=10000

IpPortToStringConverter : convert source=hello.typeconverter.type.IpPort@59cb0946

 

 - ${{number}} : 뷰 템플릿은 데이터를 문자로 출력한다.

   따라서 컨버터를 적용하게 되면 Integer타입인 10000을 String 타입으로 변환하는 컨버터인

   IntegerToStringConverter를 실행하게 된다. 

   이 부분은 컨버터를 실행하지 않아도 타임리프가 숫자를 문자로 자동으로 변환하기 때문에

   컨버터를 적용할 때와 하지 않을 때가 같다.

 

 - ${{ipPort}} : 뷰 템플릿은 데이터를 문자로 출력한다.

   따라서 컨버터를 적용하게 되면 IpPort 타입을 String 타입으로 변환해야 하므로 IpPortToStringConverter가 적용된다.

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

 

폼에 적용하기

 

ConverterController - converterForm 추가

 

package hello.typeconverter.controller;

import hello.typeconverter.type.IpPort;
import lombok.Data;
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;

@Controller
public class ConvertorController {

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

 

Form 객체를 데이터 전달 폼 객체로 사용한다.

 - GET /converter-edit : IpPort를 뷰 템플릿 폼에 출력한다

 - POST /converter-edit : 뷰 템플릿 폼의 IpPort 정보를 받아서 출력한다.

 

resources/templates/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 는 컨버터 적용이 된다. -->
    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 는 id, name를 출력하는 등의 다양한 기능이 있는데, 여기에 컨버전 서비스도 함께 적용된다.

 

실행 : http://localhost:8080/converter-edit

실행 로그

 

 

- GET /converter-edit

 : th:field가 자동으로 컨버전 서비스를 적용해주어서 ${{ipPort}} 처럼 적용되었다. 따라서 IpPort → String으로 변환된다.

- POST /converter-edit

 : @ModelAttribute를 사용해서 String → IpPort 로 변환된다.

 

 

728x90
반응형

댓글