Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 未通过所有参数构造函数设置多部分/表单数据MutlipartFile 背景_Java_Spring Boot_Rest - Fatal编程技术网

Java 未通过所有参数构造函数设置多部分/表单数据MutlipartFile 背景

Java 未通过所有参数构造函数设置多部分/表单数据MutlipartFile 背景,java,spring-boot,rest,Java,Spring Boot,Rest,这个问题是在我的日常工作中出现的,当时我正在为带有一些元信息的文件上传实现一个POST multipart/form data端点。我不是春靴生态系统的专家;这个问题很可能是通过一个简单的修复来解决的,而我只是缺少了一个合适的搜索词 问题陈述 为了实现带有附加元信息的文件上传端点,我编写了以下@RestController: @RestController @RequestMapping(Resource.ROOT) @AllArgsConstructor(onConstructor = @__

这个问题是在我的日常工作中出现的,当时我正在为带有一些元信息的文件上传实现一个
POST multipart/form data
端点。我不是春靴生态系统的专家;这个问题很可能是通过一个简单的修复来解决的,而我只是缺少了一个合适的搜索词

问题陈述 为了实现带有附加元信息的文件上传端点,我编写了以下
@RestController

@RestController
@RequestMapping(Resource.ROOT)
@AllArgsConstructor(onConstructor = @__({@Inject}))
public class Resource {
  public static final String ROOT = "/test";

  private final Logger logger;

  @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
  public ResponseEntity<Void> test(@Valid final Request request) {
    logger.info("request = {}", request);
    return ResponseEntity.ok().build();
  }
}
还有一个小的快乐路径测试:

@SpringBootTest
@AutoConfigureMockMvc
class TestCase {

  @Autowired
  private MockMvc mockMvc;

  @Test
  void shouldReturnOk() throws Exception {
    // GIVEN
    final byte[] content = Files.readAllBytes(Path.of(".", "src/test/resources/PenPen.png"));
    final String name = "name";

    // WHEN
    // @formatter:off
    mockMvc.perform(MockMvcRequestBuilders
        .multipart(Resource.ROOT)
            .file("file", content)
            .param("name", name))

    // THEN
        .andExpect(status().isOk());
    // @formatter:on
  }
}
完整的MRE可在上找到

运行测试(
/mvnw test)
时,测试失败,端点返回的是
400错误请求,而不是
200 OK
。读取日志会发现请求参数
文件
null

...
     Content type = text/plain;charset=UTF-8
             Body = file: must not be null.
...
我部分理解为什么它是空的。有了这部分知识,我就能够通过使
请求中的
文件
字段变为可变字段来规避这个问题:

@ToString
@Getter
@AllArgsConstructor
public class Request {

  @NotNull
  private final String name;

  @Setter
  @NotNull
  private MultipartFile file;
}
可以在上找到“修复”问题的代码

然而,这使得
请求
是可变的,这是我想要阻止的。为了进一步调查,我在
Request
上展开了lombok注释,并添加了一些日志记录:

public class Request {

  private static final Logger LOGGER = LoggerFactory.getLogger(Request.class);

  @NotNull
  private final String name;

  @NotNull
  private MultipartFile file;

  public Request(final String name, final MultipartFile file) {
    this.name = name;
    this.setFile(file);
  }

  public @NotNull String getName() {
    return this.name;
  }

  public @NotNull MultipartFile getFile() {
    return this.file;
  }

  public String toString() {
    return "Request(name=" + this.getName() + ", file=" + this.getFile() + ")";
  }

  public void setFile(final MultipartFile file) {
    LOGGER.info("file = {}", file);
    this.file = file;
  }
}
可在上找到展开版本的代码

当查看现在成功的测试的日志语句时,我们可以看到
Request::setFile
被调用了两次:

2020-09-05 09:42:31.049  INFO 11012 --- [           main] d.turing85.springboot.multipart.Request  : file = null
2020-09-05 09:42:31.056  INFO 11012 --- [           main] d.turing85.springboot.multipart.Request  : file = org.springframework.mock.web
第一个调用来自构造函数调用。我想,第二个调用来自Spring的表单参数映射机制中的某个地方

我知道有可能在端点上单独定义表单参数,并在方法中构造
请求
实例:

public class Resource {
  ...
  @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
  public ResponseEntity<Void> test(
      @RequestPart(name = "name") final String name,
      @RequestPart(name = "file") final MultipartFile file) {
    final Request request = new Request(name, file);
    logger.info("request = {}", request);
    return ResponseEntity.ok().build();
  }
}
公共类资源{
...
@PostMapping(使用=MediaType.MULTIPART\u FORM\u数据,生成=MediaType.APPLICATION\u JSON)
公众反应测试(
@RequestPart(name=“name”)最终字符串名称,
@RequestPart(name=“file”)最终多部分文件{
最终请求=新请求(名称、文件);
info(“request={}”,request);
返回ResponseEntity.ok().build();
}
}
然而,这将导致其他问题。例如,我们必须为
MissingServletRequestPartException
添加一个额外的异常映射器,并将返回的HTTP响应与
BindException
的现有响应对齐。如果可能的话,我希望避免这种情况

出现了对该主题的搜索。然而,这个解决方案并不适合我,因为我不使用MVC(我想)

问题: 是否有可能保持
请求
不可变,以便Spring能够将
多部分文件
传递给all args构造函数,而不是事后通过setter进行设置?编写自定义映射器/转换器是可以接受的,但我没有发现为特定端点或特定类型编写映射器的可能性

 @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<Void> test(@Valid @ModelAttribute final RequestDto request) {
     return ResponseEntity.ok().build();
 }
上面的代码使用ModelAttribute

@SpringBootTest
@AutoConfigureMockMvc
class FileUploadControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnOk() throws Exception {
        // GIVEN
        final byte[] content = Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader().getResource("text.txt").toURI()));
        final String name = "name";

        // WHEN
        // @formatter:off
        mockMvc.perform(MockMvcRequestBuilders
                .multipart("/context/api/v1")
                .file("multipartFile", content)
                .param("name", name))

                // THEN
                .andExpect(status().isOk());
        // @formatter:on
    }
}
你也给出了绝对路径,我猜这是错误的。您可以使用类加载器获取文件

上面的代码使用ModelAttribute

@SpringBootTest
@AutoConfigureMockMvc
class FileUploadControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnOk() throws Exception {
        // GIVEN
        final byte[] content = Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader().getResource("text.txt").toURI()));
        final String name = "name";

        // WHEN
        // @formatter:off
        mockMvc.perform(MockMvcRequestBuilders
                .multipart("/context/api/v1")
                .file("multipartFile", content)
                .param("name", name))

                // THEN
                .andExpect(status().isOk());
        // @formatter:on
    }
}

你也给出了绝对路径,我猜这是错误的。您可以使用类加载器获取文件。

是否需要包装
多部分文件
?似乎如果您将其分离,您将不会遇到可变性问题。我尝试使用ModelAttribute,它可以工作,但您必须为多部分数据定义setter,否则它将为空。@limido是的,它是空的。我在我的帖子中解释了为什么我希望它出现在
Request
中。@Gurkanİlleez定义的二传手不符合这一点。我已经演示了为
文件提供setter
“修复”了问题,而没有添加
@modeldattribute
。当然,它不能解决这一点,但转换器正在使用setter转换多部分文件数据是否需要包装
多部分文件
?似乎如果您将其分离,您将不会遇到可变性问题。我尝试使用ModelAttribute,它可以工作,但您必须为多部分数据定义setter,否则它将为空。@limido是的,它是空的。我在我的帖子中解释了为什么我希望它出现在
Request
中。@Gurkanİlleez定义的二传手不符合这一点。我已经演示了为
文件提供setter
“修复”了问题,而没有添加
@modeldattribute
。当然,它不能解决这一点,但是转换器正在使用setter为我转换多部分文件数据,测试仍然失败
@ModelAttriibute
。这并不能回答问题。至于我为什么想要不变性:对于JSON请求,我们可以对DTO进行注释,以便使用bulder。对于表单数据,情况似乎并非如此。这反过来导致DTO的异属结构,这反过来又引入了不必要的复杂性。对我来说,使用
@modelatriibute
测试仍然失败。这并不能回答问题。至于我为什么想要不变性:对于JSON请求,我们可以对DTO进行注释,以便使用bulder。对于表单数据,情况似乎并非如此。这反过来导致DTO的异属结构,这反过来又引入了不必要的复杂性。