Browse Source

Support for @RequestParam Map declared with MultipartFile/Part values

Issue: SPR-17405
pull/1999/head
Juergen Hoeller 6 years ago
parent
commit
f0f1979fc5
  1. 98
      spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java
  2. 38
      spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java
  3. 150
      spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java
  4. 55
      src/docs/asciidoc/web/webmvc.adoc

98
spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolver.java

@ -16,10 +16,14 @@ @@ -16,10 +16,14 @@
package org.springframework.web.method.annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -29,22 +33,29 @@ import org.springframework.web.bind.support.WebDataBinderFactory; @@ -29,22 +33,29 @@ import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.MultipartResolutionDelegate;
/**
* Resolves {@link Map} method arguments annotated with an @{@link RequestParam}
* where the annotation does not specify a request parameter name.
* See {@link RequestParamMethodArgumentResolver} for resolving {@link Map}
* method arguments with a request parameter name.
*
* <p>The created {@link Map} contains all request parameter name/value pairs.
* If the method parameter type is {@link MultiValueMap} instead, the created
* map contains all request parameters and all there values for cases where
* request parameters have multiple values.
* <p>The created {@link Map} contains all request parameter name/value pairs,
* or all multipart files for a given parameter name if specifically declared
* with {@link MultipartFile} as the value type. If the method parameter type is
* {@link MultiValueMap} instead, the created map contains all request parameters
* and all their values for cases where request parameters have multiple values
* (or multiple multipart files of the same name).
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
* @see RequestParamMethodArgumentResolver
* @see HttpServletRequest#getParameterMap()
* @see MultipartRequest#getMultiFileMap()
* @see MultipartRequest#getFileMap()
*/
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@ -59,26 +70,71 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum @@ -59,26 +70,71 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
Map<String, String[]> parameterMap = webRequest.getParameterMap();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
// MultiValueMap
Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
}
else if (valueType == Part.class) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
result.add(part.getName(), part);
}
return result;
}
});
return result;
return new LinkedMultiValueMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
}
});
return result;
}
}
else {
Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
// Regular Map
Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
}
else if (valueType == Part.class) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedHashMap<String, Part> result = new LinkedHashMap<>(parts.size());
for (Part part : parts) {
if (!result.containsKey(part.getName())) {
result.put(part.getName(), part);
}
}
return result;
}
});
return result;
return new LinkedHashMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
return result;
}
}
}
}

38
spring-web/src/main/java/org/springframework/web/multipart/support/MultipartResolutionDelegate.java

@ -25,8 +25,10 @@ import javax.servlet.http.Part; @@ -25,8 +25,10 @@ import javax.servlet.http.Part;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.util.WebUtils;
/**
@ -44,6 +46,19 @@ public abstract class MultipartResolutionDelegate { @@ -44,6 +46,19 @@ public abstract class MultipartResolutionDelegate {
public static final Object UNRESOLVABLE = new Object();
@Nullable
public static MultipartRequest resolveMultipartRequest(NativeWebRequest webRequest) {
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
return multipartRequest;
}
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && isMultipartContent(servletRequest)) {
return new StandardMultipartHttpServletRequest(servletRequest);
}
return null;
}
public static boolean isMultipartRequest(HttpServletRequest request) {
return (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null ||
isMultipartContent(request));
@ -103,13 +118,13 @@ public abstract class MultipartResolutionDelegate { @@ -103,13 +118,13 @@ public abstract class MultipartResolutionDelegate {
}
}
else if (Part.class == parameter.getNestedParameterType()) {
return (isMultipart ? resolvePart(request, name) : null);
return (isMultipart ? request.getPart(name): null);
}
else if (isPartCollection(parameter)) {
return (isMultipart ? resolvePartList(request, name) : null);
}
else if (isPartArray(parameter)) {
return (isMultipart ? resolvePartArray(request, name) : null);
return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null);
}
else {
return UNRESOLVABLE;
@ -144,12 +159,8 @@ public abstract class MultipartResolutionDelegate { @@ -144,12 +159,8 @@ public abstract class MultipartResolutionDelegate {
return null;
}
private static Part resolvePart(HttpServletRequest servletRequest, String name) throws Exception {
return servletRequest.getPart(name);
}
private static List<Part> resolvePartList(HttpServletRequest servletRequest, String name) throws Exception {
Collection<Part> parts = servletRequest.getParts();
private static List<Part> resolvePartList(HttpServletRequest request, String name) throws Exception {
Collection<Part> parts = request.getParts();
List<Part> result = new ArrayList<>(parts.size());
for (Part part : parts) {
if (part.getName().equals(name)) {
@ -159,15 +170,4 @@ public abstract class MultipartResolutionDelegate { @@ -159,15 +170,4 @@ public abstract class MultipartResolutionDelegate {
return result;
}
private static Part[] resolvePartArray(HttpServletRequest servletRequest, String name) throws Exception {
Collection<Part> parts = servletRequest.getParts();
List<Part> result = new ArrayList<>(parts.size());
for (Part part : parts) {
if (part.getName().equals(name)) {
result.add(part);
}
}
return result.toArray(new Part[0]);
}
}

150
spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,74 +18,68 @@ package org.springframework.web.method.annotation; @@ -18,74 +18,68 @@ package org.springframework.web.method.annotation;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.Part;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockMultipartFile;
import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
import org.springframework.mock.web.test.MockPart;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ResolvableMethod;
import org.springframework.web.multipart.MultipartFile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.method.MvcAnnotationPredicates.requestParam;
import static org.junit.Assert.*;
import static org.springframework.web.method.MvcAnnotationPredicates.*;
/**
* Test fixture with {@link RequestParamMapMethodArgumentResolver}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class RequestParamMapMethodArgumentResolverTests {
private RequestParamMapMethodArgumentResolver resolver;
private RequestParamMapMethodArgumentResolver resolver = new RequestParamMapMethodArgumentResolver();
private NativeWebRequest webRequest;
private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletRequest request;
private NativeWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
@Before
public void setUp() throws Exception {
resolver = new RequestParamMapMethodArgumentResolver();
request = new MockHttpServletRequest();
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
}
@Test
public void supportsParameter() {
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class);
assertTrue(resolver.supportsParameter(param));
param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class);
param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class);
assertTrue(resolver.supportsParameter(param));
param = this.testMethod.annot(requestParam().name("name")).arg(Map.class);
param = this.testMethod.annot(requestParam().name("name")).arg(Map.class, String.class, String.class);
assertFalse(resolver.supportsParameter(param));
param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class);
param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class, String.class, String.class);
assertFalse(resolver.supportsParameter(param));
}
@Test
public void resolveMapArgument() throws Exception {
public void resolveMapOfString() throws Exception {
String name = "foo";
String value = "bar";
request.addParameter(name, value);
Map<String, String> expected = Collections.singletonMap(name, value);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
@ -93,7 +87,7 @@ public class RequestParamMapMethodArgumentResolverTests { @@ -93,7 +87,7 @@ public class RequestParamMapMethodArgumentResolverTests {
}
@Test
public void resolveMultiValueMapArgument() throws Exception {
public void resolveMultiValueMapOfString() throws Exception {
String name = "foo";
String value1 = "bar";
String value2 = "baz";
@ -103,19 +97,115 @@ public class RequestParamMapMethodArgumentResolverTests { @@ -103,19 +97,115 @@ public class RequestParamMapMethodArgumentResolverTests {
expected.add(name, value1);
expected.add(name, value2);
MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class);
MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
assertEquals("Invalid result", expected, result);
}
@Test
@SuppressWarnings("unchecked")
public void resolveMapOfMultipartFile() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected1 = new MockMultipartFile("mfile", "Hello World".getBytes());
MultipartFile expected2 = new MockMultipartFile("other", "Hello World 3".getBytes());
request.addFile(expected1);
request.addFile(expected2);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, MultipartFile.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
Map<String, MultipartFile> resultMap = (Map<String, MultipartFile>) result;
assertEquals(2, resultMap.size());
assertEquals(expected1, resultMap.get("mfile"));
assertEquals(expected2, resultMap.get("other"));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMultiValueMapOfMultipartFile() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected1 = new MockMultipartFile("mfilelist", "Hello World 1".getBytes());
MultipartFile expected2 = new MockMultipartFile("mfilelist", "Hello World 2".getBytes());
MultipartFile expected3 = new MockMultipartFile("other", "Hello World 3".getBytes());
request.addFile(expected1);
request.addFile(expected2);
request.addFile(expected3);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, MultipartFile.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
MultiValueMap<String, MultipartFile> resultMap = (MultiValueMap<String, MultipartFile>) result;
assertEquals(2, resultMap.size());
assertEquals(2, resultMap.get("mfilelist").size());
assertEquals(expected1, resultMap.get("mfilelist").get(0));
assertEquals(expected2, resultMap.get("mfilelist").get(1));
assertEquals(1, resultMap.get("other").size());
assertEquals(expected3, resultMap.get("other").get(0));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMapOfPart() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("multipart/form-data");
Part expected1 = new MockPart("mfile", "Hello World".getBytes());
Part expected2 = new MockPart("other", "Hello World 3".getBytes());
request.addPart(expected1);
request.addPart(expected2);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, Part.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
Map<String, Part> resultMap = (Map<String, Part>) result;
assertEquals(2, resultMap.size());
assertEquals(expected1, resultMap.get("mfile"));
assertEquals(expected2, resultMap.get("other"));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMultiValueMapOfPart() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("multipart/form-data");
Part expected1 = new MockPart("mfilelist", "Hello World 1".getBytes());
Part expected2 = new MockPart("mfilelist", "Hello World 2".getBytes());
Part expected3 = new MockPart("other", "Hello World 3".getBytes());
request.addPart(expected1);
request.addPart(expected2);
request.addPart(expected3);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, Part.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
MultiValueMap<String, Part> resultMap = (MultiValueMap<String, Part>) result;
assertEquals(2, resultMap.size());
assertEquals(2, resultMap.get("mfilelist").size());
assertEquals(expected1, resultMap.get("mfilelist").get(0));
assertEquals(expected2, resultMap.get("mfilelist").get(1));
assertEquals(1, resultMap.get("other").size());
assertEquals(expected3, resultMap.get("other").get(0));
}
public void handle(
@RequestParam Map<?, ?> param1,
@RequestParam MultiValueMap<?, ?> param2,
@RequestParam("name") Map<?, ?> param3,
Map<?, ?> param4) {
@RequestParam Map<String, String> param1,
@RequestParam MultiValueMap<String, String> param2,
@RequestParam Map<String, MultipartFile> param3,
@RequestParam MultiValueMap<String, MultipartFile> param4,
@RequestParam Map<String, Part> param5,
@RequestParam MultiValueMap<String, Part> param6,
@RequestParam("name") Map<String, String> param7,
Map<String, String> param8) {
}
}

55
src/docs/asciidoc/web/webmvc.adoc

@ -1790,10 +1790,11 @@ and others) and is equivalent to `required=false`. @@ -1790,10 +1790,11 @@ and others) and is equivalent to `required=false`.
| For access to name-value pairs in URI path segments. See <<mvc-ann-matrix-variables>>.
| `@RequestParam`
| For access to the Servlet request parameters. Parameter values are converted to the declared
method argument type. See <<mvc-ann-requestparam>>.
| For access to the Servlet request parameters, including multipart files. Parameter values
are converted to the declared method argument type. See <<mvc-ann-requestparam>> as well
as <<mvc-multipart-forms>>.
Note that use of `@RequestParam` is optional (for example, to set its attributes).
Note that use of `@RequestParam` is optional for simple parameter values.
See "`Any other argument`", at the end of this table.
| `@RequestHeader`
@ -1809,12 +1810,12 @@ and others) and is equivalent to `required=false`. @@ -1809,12 +1810,12 @@ and others) and is equivalent to `required=false`.
argument type by using `HttpMessageConverter` implementations. See <<mvc-ann-requestbody>>.
| `HttpEntity<B>`
| For access to request headers and body. The body is converted with `HttpMessageConverter` implementations.
| For access to request headers and body. The body is converted with an `HttpMessageConverter`.
See <<mvc-ann-httpentity>>.
| `@RequestPart`
| For access to a part in a `multipart/form-data` request.
See <<mvc-multipart-forms>>.
| For access to a part in a `multipart/form-data` request, converting the part's body
with an `HttpMessageConverter`. See <<mvc-multipart-forms>>.
| `java.util.Map`, `org.springframework.ui.Model`, `org.springframework.ui.ModelMap`
| For access to the model that is used in HTML controllers and exposed to templates as
@ -2073,8 +2074,8 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa @@ -2073,8 +2074,8 @@ To get all matrix variables, you can use a `MultiValueMap`, as the following exa
----
====
Note that you need to enable the use of matrix variables. In the MVC Java configuration, you need
to set a `UrlPathHelper` with `removeSemicolonContent=false` through
Note that you need to enable the use of matrix variables. In the MVC Java configuration,
you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through
<<mvc-config-path-matching>>. In the MVC XML namespace, you can set
`<mvc:annotation-driven enable-matrix-variables="true"/>`.
@ -2084,8 +2085,8 @@ to set a `UrlPathHelper` with `removeSemicolonContent=false` through @@ -2084,8 +2085,8 @@ to set a `UrlPathHelper` with `removeSemicolonContent=false` through
==== `@RequestParam`
[.small]#<<web-reactive.adoc#webflux-ann-requestparam,Same as in Spring WebFlux>>#
You can use the `@RequestParam` annotation to bind Servlet request parameters (that is, query
parameters or form data) to a method argument in a controller.
You can use the `@RequestParam` annotation to bind Servlet request parameters (that is,
query parameters or form data) to a method argument in a controller.
The following example shows how to do so:
@ -2114,17 +2115,20 @@ The following example shows how to do so: @@ -2114,17 +2115,20 @@ The following example shows how to do so:
====
By default, method parameters that use this annotation are required, but you can specify that
a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to `false`
or by declaring the argument with an `java.util.Optional` wrapper.
a method parameter is optional by setting the `@RequestParam` annotation's `required` flag to
`false` or by declaring the argument with an `java.util.Optional` wrapper.
Type conversion is automatically applied if the target method parameter type is not
`String`. See <<mvc-ann-typeconversion>>.
Declaring the argument type as an array or list allows for resolving multiple parameter
values for the same parameter name.
When an `@RequestParam` annotation is declared as a `Map<String, String>` or
`MultiValueMap<String, String>` argument, the map is populated with all request
parameters.
`MultiValueMap<String, String>`, without a parameter name specified in the annotation,
then the map is populated with the request parameter values for each given parameter name.
Note that use of `@RequestParam` is optional, (for example, to set its attributes).
Note that use of `@RequestParam` is optional (for example, to set its attributes).
By default, any argument that is a simple value type (as determined by
{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver, is treated as if it were annotated
@ -2534,8 +2538,8 @@ after the redirect, attributes from the "`input`" `FlashMap` are automatically a @@ -2534,8 +2538,8 @@ after the redirect, attributes from the "`input`" `FlashMap` are automatically a
.Matching requests to flash attributes
****
The concept of flash attributes exists in many other web frameworks and has proven to sometimes be
exposed to concurrency issues. This is because, by definition, flash attributes
The concept of flash attributes exists in many other web frameworks and has proven to sometimes
be exposed to concurrency issues. This is because, by definition, flash attributes
are to be stored until the next request. However the very "`next`" request may not be the
intended recipient but another asynchronous request (for example, polling or resource requests),
in which case the flash attributes are removed too early.
@ -2577,16 +2581,21 @@ public class FileUploadController { @@ -2577,16 +2581,21 @@ public class FileUploadController {
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
----
====
NOTE: When you use Servlet 3.0 multipart parsing, you can also use `javax.servlet.http.Part`,
instead of Spring's `MultipartFile`, as a method argument
Declaring the argument type as a `List<MultipartFile>` allows for resolving multiple
files for the same parameter name.
When the `@RequestParam` annotation is declared as a `Map<String, MultipartFile>` or
`MultiValueMap<String, MultipartFile>`, without a parameter name specified in the annotation,
then the map is populated with the multipart files for each given parameter name.
NOTE: With Servlet 3.0 multipart parsing, you may also declare `javax.servlet.http.Part`
instead of Spring's `MultipartFile`, as a method argument or collection value type.
You can also use multipart content as part of data binding to a
<<mvc-ann-modelattrib-method-args,command object>>. For example, the form field
@ -2604,7 +2613,6 @@ class MyForm { @@ -2604,7 +2613,6 @@ class MyForm {
private MultipartFile file;
// ...
}
@Controller
@ -2612,16 +2620,13 @@ public class FileUploadController { @@ -2612,16 +2620,13 @@ public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
if (!form.getFile().isEmpty()) {
byte[] bytes = form.getFile().getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
----
====

Loading…
Cancel
Save