상황
컨트롤러 단에서 Get Mapping 시, Enum 타입의 코드 값을 넘겨주고 받도록 코드를 구현했다.
그런데 해당 api 호출 시에 에러를 뱉었다.
/api/enum-test?enumType=T 로 호출 시 에러 발생 (enum 타입의 코드 값으로 호출하면 에러 발생)
/api/enum-test?enumType=TITLE 로 호출 시 정상 동작
도입
알고보니 @RequestParam @PathVariable를 통해 파라미터를 넘길 때 정수형 혹은 문자열 타입으로만 제한하여 사용할 수 있다고 한다. 따라서 Enum의 코드 타입을 변환시켜줄 수 있도록 별도의 구현이 필요하다.
일단 내가 작성한 코드를 살펴보자.
✏️EnumType.java
@Getter
@AllArgsConstructor
public enum EnumType implements Serializable, CommonType {
@JsonProperty("T")
TITLE("제목", "T"),
@JsonProperty("N")
NAME("작성자", "N");
private String desc;
private String code;
private static final Map<String, EnumType> CODE_MAP = Stream.of(values())
.collect(Collectors.toMap(EnumType::getCode, Function.identity()));
@JsonCreator
public static EnumType convertCodeToName(String value) {
return Optional.ofNullable(CODE_MAP.get(value))
.orElseThrow(() -> new IllegalArgumentException("invalid value"));
}
}
✏️ CommonType 인터페이스
public interface CommonType {
String getCode();
String getDesc();
}
✏️ Controller.java (문제가 된 코드)
view 단에서 /api/enum-test?enumType=T 로 호출 시 에러 발생 (enum 타입의 코드 값으로 호출하면 에러 발생)
/api/enum-test?enumType=TITLE 로 호출 시 정상 동작
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class TestApiController {
@GetMapping("/enum-test")
public ResponseEntity findBoardPostList(@RequestParam EnumType enumType) {
log.debug("test : {}", enumType);
return ResponseEntity.ok().build();
}
}
여기서 의문이 생길 수 있다.
enum의 코드값을 넘겨주면 에러가 발생하는데, 왜 full name 으로 호출하면 정상적으로 동작하는 걸까?
바로 Spring 에서 기본적으로 제공해주는 StringToEnumConverterFactory 덕분에 Enum의 name값으로는 매핑이 가능한 것이다.
나는 Enum의 이름이 아니라 프론트단에서 별도로 정의한 code 값으로 넘겨주고 싶다! ( /api/enum-test?enumType=T )
어떻게 해야할까?
적용 (Code 값으로 주고 받기)
✔️첫번째 방법
일단 별도의 Converter를 구현하지 않고는 컨트롤러 단에서 넘겨받는 객체가 enum의 코드값이면 안된다는 것은 알았다.
그럼 기존처럼 파라미터를 String 타입으로 받아서 해결하면 되지 않을까?
✏️ Controller.java
@GetMapping("/enum-test")
public ResponseEntity findBoardPostList(@RequestParam String enumType) {
log.debug("test : {}", EnumType.convertCodeToName(enumType).getCode()); // Enum 객체로 바꾸고, 해당 코드를 얻는다.
return ResponseEntity.ok().build();
}
📌참고로 @RequsetParam 어노테이션은 생략 가능하다. (생략 시 스프링 MVC는 내부에서 required=false를 적용)
위 코드처럼 수정했더니, /api/enum-test?enumType=T 호출 시 아래와 같이 로그에 정상적으로 찍힌다.
test : T
파라미터로 전달받은 String을 Enum 객체로 바꾸는 작업 (EnumType.convertCodeToName(enumType)) 을 진행했다.
그리고 getCode() 메소드를 이용하여 별도로 정의한 code 값으로 반환했다.
순서를 정리해보면
"Enum의 code 값을 프론트단에서 넘겨주기 -> Controller에서 String 타입으로 받기 -> Enum 객체로 바꾸기 -> Name을 code값으로 바꾸기
물론 이렇게 작업을 진행해서 service 단으로 넘겨 원하는 로직을 태워도 된다.
하지만 매번 이렇게 String 을 Enum 객체로 변경하는 작업은 매우 귀찮고 비효율적이다.
뭔가 전역적으로 변환하여 사용할 수 있도록 하는 방법은 없을까?
✔️두번째 방법
찾아보니 방법이 있다!
먼저 CommonType Interface 구현한 Enum을 대상으로 String을 Enum 객체로 변환할 수 있는 ConverterFactory를 만들자.
CommonType 인터페이스는 별도로 코드값과 설명을 작성하기 위해 만들어 뒀다. ( EnumType 클래스에 implements 하여 사용함 )
✏️ CodeToEnumConverterFactory.java
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import java.util.Arrays;
public class CodeToEnumConverterFactory implements ConverterFactory<String, Enum<? extends CommonType>> {
@Override
public <T extends Enum<? extends CommonType>> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumsConverter<>(targetType);
}
private static final class StringToEnumsConverter<T extends Enum<? extends CommonType>> implements Converter<String, T> {
private final Class<T> enumType;
private final boolean constantEnum;
public StringToEnumsConverter(Class<T> enumType) {
this.enumType = enumType;
this.constantEnum = Arrays.stream(enumType.getInterfaces()).anyMatch(i -> i == CommonType.class);
}
@Override
public T convert(String source) {
if (source.isEmpty()) {
return null;
}
T[] constants = enumType.getEnumConstants();
for (T c : constants) {
if (constantEnum) {
if (((CommonType) c).getCode().equals(source.trim())) {
return c;
}
} else {
if (c.name().equals(source.trim())) {
return c;
}
}
}
return null;
}
}
}
✏️[24.08.01 추가] 위 코드는 Null Pointer 역참조에 취약한 부분이 있어서 보안 강화를 위해 추가했다. 수정한 코드는 아래에 링크를 걸어둔다.
2024.08.01 - [Back/Java] - [JAVA][시큐어코딩] Null Pointer 역참조 취약에 대응하기
그리고 만든 CodeToEnumConverterFactory 를 Spring에 등록하면 끝.
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* CodeToEnumConverterFactory.class 등록
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new CodeToEnumConverterFactory());
}
}
RequestParam의 데이터 타입이 EnumType 인 것에 주목하자.
@GetMapping("/enum-test")
public ResponseEntity findBoardPostList(@RequestParam EnumType enumType) {
log.debug("test1 : {}", enumType);
log.debug("test2 : {}", enumType.getCode());
return ResponseEntity.ok().build();
}
/api/enum-test?enumType=T 이렇게 호출하면??
결과는 콘솔창에 아래와 같이 찍힌다!
test1 : TEST
test2 : T
'Back > Spring Boot' 카테고리의 다른 글
[Gradle] 내부망(폐쇄망, 오프라인)에서 Springboot gradle 빌드 (0) | 2024.08.14 |
---|---|
@RequestBody DTO json데이터가 null일 때 (3) | 2024.02.07 |
[SpringBoot, Tomcat] Tomcat JNDI DB 정보 암호화 (0) | 2023.10.24 |
[SpringBoot] 외장 tomcat에 JNDI 설정 (+war) (1) | 2023.10.23 |
[SpringBoot] war로 배포하기, 외장 톰캣 구동 (+Trouble Shooting) (1) | 2023.10.11 |
댓글