본문 바로가기
Java

[SpringBoot] Controller 에서 form-data 여러 파일과 json list string 같이 받기 (Converter)

by bryan.oh 2021. 8. 21.
반응형

Spring Boot
Converter
PropertyEditor
ObjectMapper

 

form-data 로 보내는 데이터를 받을 때 참고할 내용입니다.

 

아래와 같이 RestController 에 File들과 json list 형식의 텍스트를 보내야 할 때,

보내려고 하는 데이터 

 

form-data 는 넘길 수 있는 type 이 file 과 text 밖에 없습니다. 

 

늘 하던것 처럼 Class 로 Mapping 

jsonList 를 List<TestHello> 로 바인딩해보겠습니다.

이렇게 바꾸고 요청은 그대로. 실행하면?

Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.hello.bryan.model.TestHello': no matching editors or conversion strategy found

이런 오류가 발생합니다. 

그럼 리스트가 아닌 그냥 class 는 가능한가??

마찬가지입니다. 같은 오류 메시지가 나옵니다.

form-data 로 보내서 그런것 같습니다.

 

해결하기

가장 간단한 방법 : @RequestPart, @RequestParam 

Controller 에서 json string 을 ObjectMapper 로 변환하기

아래와 같이 전달 된거죠

여기에서 jsonList String 을 class 로 맵핑 시키려면, 일단 Mapping 할 Class 를 생성합니다.

@NoArgsConstructor 는 필수

그리고 ObjectMapping 으로. String 에서 List<HelloTest> 로 변환해줍니다.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

// RestController ...

    @PostMapping(value = "/test")
    public ResponseEntity<Void> test(@RequestPart("files") List<MultipartFile> files
            , @RequestParam("jsonList") String jsonList) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new SimpleModule());
        List<TestHello> testHelloList = objectMapper.readValue(jsonList, new TypeReference<>() {});
        log.debug("files count = {}", files.size());
        log.debug("TestHello count = {}", testHelloList.size());
        return new ResponseEntity<>(HttpStatus.OK);
    }

잘 받아왔습니다.

이렇게 해도되지만 !

 

좀 더 나은 방법으로.

여기에 사용할 수 있는 방법이 

1. PropertyEditorSupport 를 상속받은 mapper class 를 initBinder 를 이용해서 연결해주는 방법.
2. Converter 를 이용한 방법.
3. Formatter 를 이용한 방법.

이정도 있는데요.

각각의 특징은.

PropertyEditor의 특징
- Spring이 제공하는 DataBinder 인터페이스를 통해 사용
- Spring 3 이전까지 DataBinder가 변환 작업에 사용한 인터페이스
- 값(상태 정보)을 저장하고 있어 thread-safe하지 않음
- 일반적인 싱글톤 scope 빈으로 등록해서 사용할 수 없음
- Object - String간의 변환만 할 수 있어 사용 범위가 제한적
Converter의 특징
- Spring3 부터 추가
- 참조 타입끼리 변환 가능한 일반적인(general) 기능의 변환기
- Spring이 제공하는 ConversionService 인터페이스를 통해 사용
- 값(상태 정보)을 저장하지 않으므로 thread-safe함
- 빈으로 등록해서 사용할 수 있음
Formatter의 특징
- Spring3 부터 추가
- PropertyEditor의 대체
- Object - String 간 변환을 담당하는 web 특화 인터페이스
- Spring이 제공하는 ConversionService 인터페이스를 통해 사용
- 값(상태 정보)을 저장하지 않으므로 thread-safe함
- 빈으로 등록해서 사용할 수 있음
- 문자열을 Locale에 따라 다국화 처리 하는 기능 제공 (Optional)

 

여기서 우선 Converter 를 이용해 보겠습니다.

Converter Class 생성

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import java.util.List;


@Component
public class TestHelloConverter implements Converter<String, List<TestHello>> {

    private ObjectMapper objectMapper;
    public TestHelloConverter(ObjectMapper objectMapper){
        this.objectMapper = objectMapper;
    }

    @SneakyThrows
    @Override
    public List<TestHello> convert(String value) {
        return objectMapper.readValue(value, new TypeReference<>() {});
    }
}
  • Converter<소스, 대상> 을 상속받습니다.
  • "소스" 를 "대상" 으로 변경하기 위한 Converter 입니다.
  • 여기서는 ObjectMapper 는 생성자 주입으로 받았지만, 위에 있던 컨트롤러에서 처럼 직접 생성해도 됩니다

 

그리고 WebConfig 에 아래와 같이 Converter 를 등록해 줍니다.

WebConfig class명이 중요한게 아니고 extends WebMvcConfigurationSupport 한 class 에 하시면 됩니다.
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

	// 생략 

    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new SimpleModule())
                .registerModule(new JavaTimeModule())
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }

    @Override
    protected void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new TestHelloConverter(objectMapper()));
    }
}
이렇게 등록을 해두면, Controller 에서 String 을 받아왔는데 맵핑하는 타입이 List<HelloTest> 라면 이 위의 TestHelloConverter가 실행됩니다.

 

그리고 컨트롤러 코드를 깔끔하게 정리.

@PostMapping(value = "/test")
public ResponseEntity<Void> test(@RequestPart("files") List<MultipartFile> files
        , @RequestParam("jsonList") List<TestHello> testHelloList) throws JsonProcessingException {
    log.debug("files count = {}", files.size());
    log.debug("TestHello count = {}", testHelloList.size());
    return new ResponseEntity<>(HttpStatus.OK);
}

테스트 결과는 성공!

 

Formatter 는 나중에..

728x90
반응형

댓글