当前位置: 首页 > 知识库问答 >
问题:

multipart/form-data MutlipartFile不是通过所有args构造函数设置的

单嘉泽
2023-03-14

为了实现带有附加元信息的文件上载endpoint,我编写了以下@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();
  }
}

request指定为:

@Value
@AllArgsConstructor
public class Request {

  @NotNull
  String name;

  @NotNull
  MultipartFile file;
}

和一个快乐小路测试:

@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
  }
}

运行测试(./mvnw test)时,它失败,endpoint返回400错误请求而不是200确定。读取日志显示请求参数filenull:

...
     Content type = text/plain;charset=UTF-8
             Body = file: must not be null.
...

我部分理解为什么它是。有了这些部分知识,我可以通过使request中的字段file可变来规避这个问题:

@ToString
@Getter
@AllArgsConstructor
public class Request {

  @NotNull
  private final String name;

  @Setter
  @NotNull
  private MultipartFile file;
}

“修复”问题的代码可以在Bitbucket,branchproblem-solled-by-making-field-mutable上找到。

但是,这使得request变了,我想防止这种情况发生。为了进一步研究,我展开了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;
  }
}

展开版本的代码可以在Bitbucket,分支lombok-unrolled-for-debugging上找到。

在查看现在成功的测试的日志语句时,我们可以看到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表单参数映射机制中的某个地方。

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

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();
  }
}

然而,这将导致其他问题。例如,我们必须为MissingServletRequestPartException添加一个额外的异常映射器,并将返回的HTTP响应与Bindexception的现有响应对齐。如果可能的话,我想避免这样做。

共有1个答案

殳睿
2023-03-14
 @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();
 }

它仍然在使用rest api调用。但我真的没有得到你的一成不变的关心。

如果定义了多部分数据setter,则可以使用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一起工作。

 类似资料:
  • 当创建Gio controller对象时,可以将配置参数传递给controller,具体传递方式如下所示: var configs = {         color: {                 surface:0xFF0000         } }; var globe = new Gio.controller(container, configs);

  • 我目前正在使用java编写线性代数库,灵感来自我的线性代数I 我希望我的程序尽可能灵活,因此决定我的矩阵类将有许多可用的构造函数。然而,当我试图将这两个构造函数结合在一起时,Java抛出了一个编译错误: 我不明白为什么,因为第一个构造函数将接受任意数量的数组作为args:而第二个构造函数将只接收一个arg:。我意识到在两个构造函数中,我将处理相同的数据结构(双的2D数组),但这里没有关于输入类型的

  • 1.如何通过参数化构造函数设置这些属性?? 类文件 > 我想通过传递参数构造函数的setter设置这些属性,但它没有设置这些属性 在这里,我想通过toString方法显示属性 }对象文件 为什么我无法通过构造函数设置这些属性?? public class Runnable{public static void main(String[]args){Product p1=新产品(“pc”,“Grey

  • 问题内容: 我有两个类,和,扩展了前一个类。 具有以下构造函数: 我将注意到所有实例变量都已设置为private。 同时,具有以下构造函数: 但是,这为我的构造函数引发了“找不到符号”错误。 我尝试使用,但是我的超类的私有范围阻止了这种情况。 我发现向我的构造函数中添加字段并允许我调用超级构造函数,但是我想知道是否存在一种无需在子类构造函数中传递其所有参数的情况下调用超级构造函数的方法? 问题答案

  • 问题内容: 我在一个Activity中有一个IntentService,当我尝试调用该服务时,它将引发此错误,我发现这很奇怪,因为如果我声明了空的构造函数。 错误: AndroidManifest.xml 活动: 问题答案: 你是一个内部阶级。如果要将其保留在内部,请将其更改为static: 您可能需要阅读不同类型的嵌套类。google的第一个链接:http : //docs.oracle.com

  • 问题内容: 到目前为止,我已经看到了两种在Java中设置变量值的方法。有时使用带有参数的构造函数,而其他setter方法则用于设置每个变量的值。 我知道,一旦使用“新”关键字实例化了一个类,构造函数就会在类内部初始化一个实例变量。 但是,什么时候使用构造函数,何时使用setter? 问题答案: 当您要创建对象的新实例时,应使用构造函数方法,该实例的值已填充(准备使用的对象中已填充值)。这样,您无需