spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.58k stars 38.13k forks source link

javax.servelet.http.Part field cannot be resolved with StandardMultipartHttpServletRequest. #27819

Closed binchoo closed 2 years ago

binchoo commented 2 years ago

Affects: 5.3.2 Related issue: #15220

Hello, I'm trying to upload a file via multipart/form-data request. The handler method requires one object parameter, which contains a field of type javax.servelet.http.Part. As MultipartFile typed field can be resolved with parts, I've expected the same from Part.

  @Setter
  @Getter
  @AllArgsConstructor
  @NoArgsConstructor
  public class UserHavingPartVO {
      private String name;
      private int age; 
      private Part file;
  }

  @Controller
  public class FileUploadController {

      @PostMapping("/vopart")
      public ResponseEntity bindVOHavingPart(UserHavingPartVO userVo){
          if (userVo.getFile() == null)
              return ResponseEntity.noContent().build();
          return ResponseEntity.ok().build();
      }
  }

As described in senario 7 (from related issue https://github.com/spring-projects/spring-framework/issues/15220), Part field could not be bind with values, as StandardMultipartHttpServletRequest wraps Part into private class StandardMultipartHttpServletRequest.StandardMultipartFile. In turn, conversion from StandardMultipartFile to Part had occured but failed.

Test and generated logs are below. I hope available approaches are found for this scenario.

@SpringJUnitWebConfig(classes = {WebConfig.class})
public class FileUploadControllerTests {

    MockMvc mvc;

    FileUploadController controller = new FileUploadController();

    MockPart mockPart = new MockPart("file", "filename.png", "file".getBytes());

    @BeforeEach
    public void setup() {
        this.mvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @DisplayName("params + Part -> VO Having Part : 400 Error")
    @Test
    void bindVOHavingPart() throws Exception {
        MultipartHttpServletRequest request = (MultipartHttpServletRequest)
                mvc.perform(multipart("/vopart")
                        .part(mockPart)
                        .param("name", "jaebin-joo")
                        .param("age", "11"))
                    .andExpect(status().is4xxClientError()) // StandardMultipartFile 타입을 Part로 변환할 수 없기 때문에
                    .andReturn().getRequest();

        assertThat(request.getParts().size()).isEqualTo(1);
        assertThat(request.getMultiFileMap().size()).isEqualTo(0);
    }
}
main] DEBUG org.springframework.web.method.HandlerMethod - Could not resolve parameter [0] in public org.springframework.http.ResponseEntity org.binchoo.study.spring.multipart.profileservice.controller.FileUploadControllerTests$FileUploadController.bindVOHavingPart(org.binchoo.study.spring.multipart.profileservice.controller.FileUploadControllerTests$UserHavingPartVO): org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'userHavingPartVO' on field 'file': rejected value [org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@4e974b9e]; codes [typeMismatch.userHavingPartVO.file,typeMismatch.file,typeMismatch.javax.servlet.http.Part,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userHavingPartVO.file,file]; arguments []; default message [file]]; default message [Failed to convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'javax.servlet.http.Part'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'javax.servlet.http.Part': no matching editors or conversion strategy found]
binchoo commented 2 years ago

PR: https://github.com/spring-projects/spring-framework/pull/27830 replaces this issue.