스프링 타입 컨버터
스프링 강의는 스프링부트 2버전이었지만
필자는 더 이상 스프링 공식 사이트에서 2버전에 대한 지원을 종료하여
자바 17버전으로 업그레이드, 스프링부트 3버전을 사용합니다.
스프링 타입 컨버터?
문자 → 숫자, 숫자 → 문자로 변환해야 하는 것처럼 애플리케이션을 개발하다보면 타입 변환이 필요한 경우 많다.
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
으로 실행
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 쿼리 스트링으로 전달하는 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에 넣은 스프링 빈 정보 반환
- 뷰를 렌더링 할 때
스프링과 타입 변환
스프링이 중간에 타입 변환기를 사용해서 타입을 String → Integer 로 변환해줬기 때문에
개발자는 편리하게 해당 타입을 바로 받을 수 있다
숫자를 문자로 변경하는것도 가능하고, Boolean 타입을 숫자로 변경하는것도 가능하다.
만약 개발자가 새로운 타입을 만들어서 반환하고 싶으면 어떻게 해야할까?
컨버터 인터페이스
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
스프링은 확장 가능한 인터페이스를 제공한다.
개발자는 스프링에 추가적인 타입 변환이 필요하면 이 컨버터 인터페이스를 구현해서 등록하면 된다.
이 컨버터 인터페이스는 모든 타입에 적용할 수 있다.
필요하면 X → Y 타입으로 변환하는 컨버터 인터페이스를 만들고,
또 Y → X 타입으로 변환하는 컨버터 인터페이스를 만들어서 등록하면 된다,
예를 들면 문자로 "true" 가 오면 Boolean 타입으로 받고 싶으면
String → Boolean 타입으로 변환되도록 컨버터 인터페이스를 만들어서 등록하고 ,반대로 적용하고 싶으면
Boolean → String 타입으로 변환되도록 컨버터를 추가로 만들어서 등록하면 된다.
과거에는 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
※ 스프링은 문자, 숫자, 불린, Enum 등 일반적인 타입에 대한 대부분의 컨버터를 기본적으로 제공한다
IDE에서 Converter, ConverterFactory, GenericConverter 의 구현체를 찾아보면 수 많은 컨버터를 확인할 수 있다.
'스프링 > 스프링 웹 개발 활용' 카테고리의 다른 글
[Spring] Converter 적용하기 (스프링과 뷰 템플릿에 적용) (1) | 2024.01.07 |
---|---|
[Spring] 스프링 컨버전 서비스 - ConversionService, 그리고 인터페이스 분리 원칙(ISP) (1) | 2023.12.28 |
[Spring] API 예외 처리 - @ExceptionHandler 와 @ControllerAdvice (0) | 2023.12.04 |
[Spring] API 예외 처리 - 스프링이 제공하는 ExceptionResolver (1) | 2023.12.03 |
[Spring] API 예외처리 - HandlerExceptionResolver (1) | 2023.12.01 |
댓글