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

[Spring] 스프링 타입 컨버터 - 타입 컨버터

by drCode 2023. 12. 6.
728x90
반응형

스프링 타입 컨버터

 

스프링 강의는 스프링부트 2버전이었지만

필자는 더 이상 스프링 공식 사이트에서 2버전에 대한 지원을 종료하여 

자바 17버전으로 업그레이드, 스프링부트 3버전을 사용합니다.

 

typeconverter.zip
0.05MB

 

https://start.spring.io/

 

 

스프링 타입 컨버터?

문자 → 숫자, 숫자 → 문자로 변환해야 하는 것처럼 애플리케이션을 개발하다보면 타입 변환이 필요한 경우 많다.

 

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

package hello.typeconverter.controller;


import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

 

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

으로 실행

 

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

 

 

Stirng data = request.getParameter("data"); 는

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

요청 파라미터를 다른 타입으로 변환 후 사용하고 싶으면 아래처럼 숫자 형변환을 거쳐야 한다.

 

Integer intValue = Integer.valueof(data);

 

스프링 MVC가 제공하는 @RequestParam을 이용하면?

 

HelloController - helloV2 추가

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

 

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

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

잘 동작한다.

 

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

 

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

이것은 스프링이 중간에서 타입을 변환해줬기 때문에 가능한 것이다.

 

이렇듯, @ModelAttribute, @PathVariable 에서도 확인해 볼 수 있다.

 

@ModelAttribute  타입 변환

@ModelAttribute UserData data

class UserData {
	Integer data;
}

 

위 코드는 @RequestParam과 같이 문자 data=10을 숫자 10으로 받을 수 있다.

 

@PathVariable 타입 변환

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

URL 경로는 문자. /users/10 → 여기서 10도 문자 10이다.

data를 Integer 타입으로 받을 수 있는 것도 스프링이 타입 변환을 해주기 때문이다.

 

스프링 타입 변환 적용 예

 - 스프링 MVC 요청 파라미터

  : @RequestParam, @ModelAttribute, @PathVariable

 - @Value 등으로 YML 정보 읽기

 - XML에 넣은 스프링 빈 정보 반환

 - 뷰를 렌더링 할 때

 

스프링과 타입 변환

스프링이 중간에 타입 변환기를 사용해서 타입을 StringInteger 로 변환해줬기 때문에

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

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

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

 

컨버터 인터페이스

package org.springframework.core.convert.converter;

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

 

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

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

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

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

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

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

String →  Boolean  타입으로 변환되도록 컨버터 인터페이스를 만들어서 등록하고 ,반대로 적용하고 싶으면

BooleanString 타입으로 변환되도록 컨버터를 추가로 만들어서 등록하면 된다.

 

과거에는 PropertyEditor 라는 것으로 타입을 변환했다.

PropertyEditor는 동시성 문제가 있어서 타입을 변환할 때마다 객체를 계속 생성해야하는 단점이 있다.

지금은 Converter 의 등장으로 해당 문제들이 해결됐고, 기능 확장이 필요하면 Converter를 사용하면 된다.

 

 

타입 컨버터 - Converter

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

 

Converter 라는 종류가 많으므로 org.springframework.core.convert.converter.Converter;를 사용해야 한다.

 

컨버터 인터페이스 ↓ ↓ ↓

package org.springframework.core.convert.converter;

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

 

 

StringToIntegerConverter - 문자를 숫자로 바꿔주는 타입 컨버터

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);
        Integer integer = Integer.valueOf(source);
        return integer;
    }
}

 

String → Integer 로 변환하기 때문에 소스가 String 이 된다.이 문자를 Integer.valueOf(source) 를 사용해서

숫자로 변경한 다음에 변경된 숫자를 반환하면 된다.

 

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

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("convert source={}", source);
        return String.valueOf(source);
    }
}

 

ConverterTest

package hello.typeconverter.converter;

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

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

// Alt + Enter : static import

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

 

값에 대한 비교말고도 좀 더 실용적인 예를 들면

 

객체 변환도 가능하다.

IpPort.java

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

 

IpPortToStringConverter.java

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

 

StringToIpPortConverter.java

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);
        // "127.0.0.1:8080"
        String[] split = source.split(":");
        String ip = split[0];
        int port = Integer.parseInt(split[1]);
        return new IpPort(ip, port);
    }
}

 

ConverterTest.java

package hello.typeconverter.converter;


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

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

// Alt + Enter : static import

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() {
        IpPortToStringConverter converter = new IpPortToStringConverter();
        IpPort source = new IpPort("127.0.0.1", 8080);
        String result = converter.convert(source);
        assertThat(result).isEqualTo("127.0.0.1:8080");
    }

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

}

 

테스트 결과

 

이렇게 타입 컨버터를 직접 사용하면 개발자가 직접 컨버팅하는 것이랑 큰 차이가 없다

 

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

 

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

Converter : 기본 타입 컨버터

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

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

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

 

https://docs.spring.io/spring-framework/reference/core/validation/convert.html

 

Spring Type Conversion :: Spring Framework

When you require a sophisticated Converter implementation, consider using the GenericConverter interface. With a more flexible but less strongly typed signature than Converter, a GenericConverter supports converting between multiple source and target types

docs.spring.io

 

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

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

728x90
반응형

댓글