You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2019/01/16 10:00:14 UTC

[servicecomb-java-chassis] 01/04: [SCB-1046] file upload support file array for the same name

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git

commit 122583e94aa939c20aa325d70f517aeeb94ff789
Author: heyile <25...@qq.com>
AuthorDate: Sat Dec 8 17:15:27 2018 +0800

    [SCB-1046] file upload support file array for the same name
---
 .../common/rest/codec/RestClientRequest.java       |   4 +-
 .../rest/codec/param/FormProcessorCreator.java     |  30 +++++-
 .../rest/codec/param/RestClientRequestImpl.java    |  61 +++++++++---
 .../VertxServerRequestToHttpServletRequest.java    |  10 ++
 .../apache/servicecomb/it/testcase/TestUpload.java |  82 ++++++++++++++++
 .../servicecomb/it/schema/UploadJaxrsSchema.java   | 108 +++++++++++++++++++++
 .../it/schema/UploadSpringmvcSchema.java           |  96 ++++++++++++++++++
 .../reference/CommonToHttpServletRequest.java      |  65 ++++++++++++-
 .../springmvc/reference/CseClientHttpRequest.java  |   3 +-
 .../core/CommonParameterTypeProcessor.java         |   5 +-
 .../swagger/generator/core/OperationGenerator.java |  23 +++++
 .../parametertype/HttpServletRequestProcessor.java |   4 +-
 ...r.java => MultipartFileArrayTypeProcessor.java} |  13 ++-
 ...or.java => MultipartFileListTypeProcessor.java} |  17 +++-
 .../parameter/MultipartFileTypeProcessor.java      |   4 +-
 ...ger.generator.core.CommonParameterTypeProcessor |   2 +
 .../swagger/invocation/converter/ConverterMgr.java |  35 ++++++-
 .../generator/InvocationContextProcessor.java      |   4 +-
 .../converter/SpringMultipartArrayConverter.java   |  56 +++++++++++
 .../converter/SpringMultipartListConverter.java    |  57 +++++++++++
 20 files changed, 640 insertions(+), 39 deletions(-)

diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
index a300273..9b36049 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/RestClientRequest.java
@@ -17,8 +17,6 @@
 
 package org.apache.servicecomb.common.rest.codec;
 
-import javax.servlet.http.Part;
-
 import io.vertx.core.MultiMap;
 import io.vertx.core.buffer.Buffer;
 
@@ -41,5 +39,5 @@ public interface RestClientRequest {
 
   Buffer getBodyBuffer() throws Exception;
 
-  void attach(String name, Part part);
+  void attach(String name, Object part);
 }
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
index 04ea5bf..65b9fb5 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/FormProcessorCreator.java
@@ -18,7 +18,10 @@
 package org.apache.servicecomb.common.rest.codec.param;
 
 import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.Part;
@@ -26,6 +29,7 @@ import javax.ws.rs.core.Response.Status;
 
 import org.apache.servicecomb.common.rest.RestConst;
 import org.apache.servicecomb.common.rest.codec.RestClientRequest;
+import org.apache.servicecomb.foundation.vertx.http.FileUploadPart;
 import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 
 import com.fasterxml.jackson.databind.JavaType;
@@ -34,6 +38,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
 import io.swagger.models.parameters.FormParameter;
 import io.swagger.models.parameters.Parameter;
 import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
 
 public class FormProcessorCreator implements ParamValueProcessorCreator {
   public static final String PARAMTYPE = "formData";
@@ -99,22 +104,41 @@ public class FormProcessorCreator implements ParamValueProcessorCreator {
   }
 
   private boolean isPart(Parameter parameter) {
-    return new FileProperty().getType().equals(((FormParameter) parameter).getType());
+    //only check
+    FormParameter formParameter = (FormParameter) parameter;
+    if ("array".equals(formParameter.getType())) {
+      Property items = formParameter.getItems();
+      if (items != null) {
+        return new FileProperty().getType().equals(items.getType());
+      }
+    }
+    return new FileProperty().getType().equals(formParameter.getType());
   }
 
-  private static class PartProcessor extends AbstractParamProcessor {
+  public static class PartProcessor extends AbstractParamProcessor {
     PartProcessor(String paramPath, JavaType targetType, Object defaultValue, boolean required) {
       super(paramPath, targetType, defaultValue, required);
     }
 
     @Override
     public Object getValue(HttpServletRequest request) throws Exception {
+
+      if (targetType.getRawClass().equals(List.class)) {
+        JavaType contentType = targetType.getContentType();
+        if (contentType != null && contentType.getRawClass().equals(Part.class)) {
+          //get all parts
+          Collection<Part> parts = request.getParts();
+          List<Part> collect = parts.stream().filter(part -> part.getName().equals(paramPath))
+              .collect(Collectors.toList());
+          return collect;
+        }
+      }
       return request.getPart(paramPath);
     }
 
     @Override
     public void setValue(RestClientRequest clientRequest, Object arg) throws Exception {
-      clientRequest.attach(paramPath, (Part) arg);
+      clientRequest.attach(paramPath, arg);
     }
 
     @Override
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
index 8b124ec..fbf56f2 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/RestClientRequestImpl.java
@@ -22,8 +22,10 @@ import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;
 
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
@@ -52,7 +54,8 @@ public class RestClientRequestImpl implements RestClientRequest {
 
   protected AsyncResponse asyncResp;
 
-  private final Map<String, Part> uploads = new HashMap<>();
+  // maybe part or list of parts
+  private final Map<String, Object> uploads = new HashMap<>();
 
   protected HttpClientRequest request;
 
@@ -80,7 +83,7 @@ public class RestClientRequestImpl implements RestClientRequest {
   }
 
   @Override
-  public void attach(String name, Part part) {
+  public void attach(String name, Object part) {
     if (null == part) {
       LOGGER.debug("null file is ignored, file name = [{}]", name);
       return;
@@ -155,35 +158,48 @@ public class RestClientRequestImpl implements RestClientRequest {
   }
 
   private void attachFiles(String boundary) {
-    Iterator<Entry<String, Part>> uploadsIterator = uploads.entrySet().iterator();
-    attachFile(boundary, uploadsIterator);
+    Iterator<Entry<String, Object>> uploadsIterator = uploads.entrySet().iterator();
+    List<NamePartMap> namePartMaps = new ArrayList<>();
+    while (uploadsIterator.hasNext()) {
+      Entry<String, Object> entry = uploadsIterator.next();
+      String name = entry.getKey();
+      Object tmpPart = entry.getValue();
+      if (tmpPart instanceof Part) {
+        namePartMaps.add(new NamePartMap(name, (Part) tmpPart));
+      } else {
+        //it's a list
+        @SuppressWarnings("unchecked")
+        List<Part> partList = (List<Part>) tmpPart;
+        partList.forEach(part -> namePartMaps.add(new NamePartMap(name, part)));
+      }
+    }
+    attachFile(boundary, namePartMaps.iterator());
   }
 
-  private void attachFile(String boundary, Iterator<Entry<String, Part>> uploadsIterator) {
+  private void attachFile(String boundary, Iterator<NamePartMap> uploadsIterator) {
     if (!uploadsIterator.hasNext()) {
       request.write(boundaryEndInfo(boundary));
       request.end();
       return;
     }
 
-    Entry<String, Part> entry = uploadsIterator.next();
+    NamePartMap next = uploadsIterator.next();
     // do not use part.getName() to get parameter name
     // because pojo consumer not easy to set name to part
-    String name = entry.getKey();
-    Part part = entry.getValue();
-    String filename = part.getSubmittedFileName();
 
-    Buffer fileHeader = fileBoundaryInfo(boundary, name, part);
+    String filename = next.getPart().getSubmittedFileName();
+
+    Buffer fileHeader = fileBoundaryInfo(boundary, next.getName(), next.getPart());
     request.write(fileHeader);
 
-    new PumpFromPart(context, part).toWriteStream(request).whenComplete((v, e) -> {
+    new PumpFromPart(context, next.getPart()).toWriteStream(request).whenComplete((v, e) -> {
       if (e != null) {
-        LOGGER.debug("Failed to sending file [{}:{}].", name, filename, e);
+        LOGGER.debug("Failed to sending file [{}:{}].", next.getName(), filename, e);
         asyncResp.consumerFail(e);
         return;
       }
 
-      LOGGER.debug("finish sending file [{}:{}].", name, filename);
+      LOGGER.debug("finish sending file [{}:{}].", next.getName(), filename);
       attachFile(boundary, uploadsIterator);
     });
   }
@@ -278,4 +294,23 @@ public class RestClientRequestImpl implements RestClientRequest {
   public MultiMap getHeaders() {
     return request.headers();
   }
+
+  public static class NamePartMap {
+    String name;
+
+    Part part;
+
+    public NamePartMap(String name, Part part) {
+      this.name = name;
+      this.part = part;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Part getPart() {
+      return part;
+    }
+  }
 }
diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
index 9274bc2..6280ef1 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerRequestToHttpServletRequest.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.foundation.vertx.http;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -275,6 +277,14 @@ public class VertxServerRequestToHttpServletRequest extends AbstractHttpServletR
     return new FileUploadPart(fileUpload);
   }
 
+  @Override
+  public Collection<Part> getParts() {
+    Set<FileUpload> fileUploads = context.fileUploads();
+    List<Part> partList = new ArrayList<>();
+    fileUploads.stream().map(FileUploadPart::new).forEach(partList::add);
+    return partList;
+  }
+
   public RoutingContext getContext() {
     return context;
   }
diff --git a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java
new file mode 100644
index 0000000..ed1459a
--- /dev/null
+++ b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestUpload.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.it.testcase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.servicecomb.it.Consumers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpEntity;
+
+public class TestUpload {
+
+  private File file1;
+
+  private File file2;
+
+  private File file3;
+
+  interface UploadIntf {
+
+  }
+
+  private static Consumers<UploadIntf> consumersSpringmvc = new Consumers<>("uploadSpringmvcSchema",
+      UploadIntf.class);
+
+  private static Consumers<UploadIntf> consumersJaxrs = new Consumers<>("uploadJaxrsSchema",
+      UploadIntf.class);
+
+  @Before
+  public void init(){
+    try {
+      file1 = File.createTempFile("jaxrstest1", ".txt");
+      file2 =  File.createTempFile("测试", ".txt");
+      file3 = File.createTempFile("files", ".yaml");
+      FileUtils.writeStringToFile(file1, "hello1");
+      FileUtils.writeStringToFile(file2, "中文 2");
+      FileUtils.writeStringToFile(file2, "cse3");
+    } catch (IOException e) {
+      Assert.fail("Failed to create temp file");
+    }
+  }
+
+  @Test
+  public void testJarxUpload1() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("file1", new FileSystemResource(file1));
+    map.put("file2", new FileSystemResource(file2));
+    String upload1 = consumersJaxrs.getSCBRestTemplate().postForObject("upload1", new HttpEntity<>(map), String.class);
+    Assert.assertTrue(constainsAll(upload1, "hello1", "中文 2"));
+  }
+
+  private static boolean constainsAll(String str, String... strings) {
+    for (String string : strings) {
+      if (!str.contains(string)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+}
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java
new file mode 100644
index 0000000..d70e894
--- /dev/null
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadJaxrsSchema.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.it.schema;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.servlet.http.Part;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+
+@RestSchema(schemaId = "uploadJaxrsSchema")
+@Path("/v1/uploadJaxrsSchema")
+public class UploadJaxrsSchema {
+  @Path("/upload1")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String fileUpload1(@FormParam("file1") Part file1, @FormParam("file2") Part file2) throws IOException {
+    return getStrFromPart(file1) + getStrFromPart(file2);
+  }
+
+  @Path("/uploadArray1")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String uploadArray1(@FormParam("file1") Part[] fileArray, @FormParam("file2") Part file2) throws IOException {
+    StringBuilder stringBuilder = new StringBuilder();
+    for (int i = 0; i < fileArray.length; i++) {
+      stringBuilder.append(getStrFromPart(fileArray[i]));
+    }
+    return stringBuilder.append(getStrFromPart(file2)).toString();
+  }
+
+
+  @Path("/uploadList1")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String uploadList1(@FormParam("file1") List<Part> fileArray, @FormParam("file2") Part file2)
+      throws IOException {
+    StringBuilder stringBuilder = new StringBuilder();
+    fileArray.forEach(part -> {
+      stringBuilder.append(getStrFromPart(part));
+    });
+    return stringBuilder.append(getStrFromPart(file2)).toString();
+  }
+
+
+  @Path("/upload2")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String fileUpload2(@FormParam("file1") Part file1, @FormParam("message") String message) throws IOException {
+    return getStrFromPart(file1) + message;
+  }
+
+  @Path("/uploadArray2")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String uploadArray2(@FormParam("file1") Part[] fileArray, @FormParam("message") String message)
+      throws IOException {
+    StringBuilder stringBuilder = new StringBuilder();
+    for (int i = 0; i < fileArray.length; i++) {
+      stringBuilder.append(getStrFromPart(fileArray[i]));
+    }
+    return stringBuilder.append(message).toString();
+  }
+
+
+  @Path("/uploadList2")
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  public String uploadList2(@FormParam("file1") List<Part> fileArray, @FormParam("message") String message)
+      throws IOException {
+    StringBuilder stringBuilder = new StringBuilder();
+    fileArray.forEach(part -> {
+      stringBuilder.append(getStrFromPart(part));
+    });
+    return stringBuilder.append(message).toString();
+  }
+
+  private static String getStrFromPart(Part file1) {
+    try (InputStream is1 = file1.getInputStream()) {
+      return IOUtils.toString(is1);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return "";
+  }
+}
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java
new file mode 100644
index 0000000..e389d2b
--- /dev/null
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/UploadSpringmvcSchema.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.it.schema;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.google.common.collect.Lists;
+
+
+@RestSchema(schemaId = "uploadSpringmvcSchema")
+@RequestMapping(path = "/v1/uploadSpringmvcSchema")
+public class UploadSpringmvcSchema {
+
+  @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String fileUpload(@RequestPart(name = "file1") MultipartFile file1,
+      @RequestPart(name = "file2") MultipartFile file2, @RequestAttribute("name") String name) {
+    return _fileUpload(Lists.newArrayList(file1, file2)) + name;
+  }
+
+  @RequestMapping(path = "/uploadArray", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String fileUploadArray(@RequestPart(name = "file1") MultipartFile[] file1,
+      @RequestPart(name = "file2") MultipartFile[] file2, @RequestAttribute("name") String name) {
+    List<MultipartFile> multipartFileList = new ArrayList<>();
+    multipartFileList.addAll(Arrays.asList(file1));
+    multipartFileList.addAll(Arrays.asList(file2));
+    return _fileUpload(multipartFileList) + name;
+  }
+
+  @RequestMapping(path = "/uploadList", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String fileUploadList(@RequestPart(name = "file1") List<MultipartFile> file1,
+      @RequestPart(name = "file2") List<MultipartFile> file2, @RequestAttribute("name") String name) {
+    file1.addAll(file2);
+    return _fileUpload(file1) + name;
+  }
+
+  @RequestMapping(path = "/uploadWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String uploadWithoutAnnotation(MultipartFile file1, MultipartFile file2,
+      @RequestAttribute("name") String name) {
+    return _fileUpload(Lists.newArrayList(file1, file2)) + name;
+  }
+
+  @RequestMapping(path = "/uploadArrayWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String uploadArrayWithoutAnnotation(MultipartFile[] file1, MultipartFile[] file2,
+      @RequestAttribute("name") String name) {
+    List<MultipartFile> multipartFileList = new ArrayList<>();
+    multipartFileList.addAll(Arrays.asList(file1));
+    multipartFileList.addAll(Arrays.asList(file2));
+    return _fileUpload(multipartFileList) + name;
+  }
+
+  @RequestMapping(path = "/uploadListArrayWithoutAnnotation", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+  public String uploadListWithoutAnnotation(List<MultipartFile> file1, List<MultipartFile> file2,
+      @RequestAttribute("name") String name) {
+    file1.addAll(file2);
+    return _fileUpload(file1) + name;
+  }
+
+  private static String _fileUpload(List<MultipartFile> fileList) {
+    StringBuilder stringBuilder = new StringBuilder();
+    try {
+      for (MultipartFile multipartFile : fileList) {
+        stringBuilder.append(IOUtils.toString(multipartFile.getBytes(), StandardCharsets.UTF_8.name()));
+      }
+    } catch (IOException e) {
+      throw new IllegalArgumentException(e);
+    }
+    return stringBuilder.toString();
+  }
+}
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
index 2d5d75e..b70a921 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CommonToHttpServletRequest.java
@@ -29,13 +29,14 @@ import java.util.Enumeration;
 import java.util.List;
 import java.util.Map;
 
-import javax.servlet.ServletException;
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.Part;
 import javax.ws.rs.core.HttpHeaders;
 
 import org.apache.servicecomb.common.rest.RestConst;
+import org.apache.servicecomb.common.rest.codec.param.FormProcessorCreator.PartProcessor;
+import org.apache.servicecomb.common.rest.definition.RestParam;
 import org.apache.servicecomb.foundation.common.part.FilePart;
 import org.apache.servicecomb.foundation.common.part.InputStreamPart;
 import org.apache.servicecomb.foundation.common.part.ResourcePart;
@@ -48,14 +49,23 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
 
   private Map<String, List<String>> httpHeaders;
 
+  //contains all the file key in the parts
+  private List<String> fileKeys = new ArrayList<>();
+
   // gen by httpHeaders
   private Cookie[] cookies;
 
   @SuppressWarnings("unchecked")
   public CommonToHttpServletRequest(Map<String, String> pathParams, Map<String, List<String>> queryParams,
-      Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData) {
+      Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData, List<RestParam> paramList) {
     setAttribute(RestConst.PATH_PARAMETERS, pathParams);
-
+    if (paramList != null && paramList.size() != 0) {
+      for (RestParam param : paramList) {
+        if (param.getParamProcessor() instanceof PartProcessor) {
+          fileKeys.add(param.getParamName());
+        }
+      }
+    }
     if (isFormData) {
       setAttribute(RestConst.FORM_PARAMETERS, (Map<String, Object>) bodyObject);
     } else {
@@ -66,6 +76,12 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
     this.httpHeaders = httpHeaders;
   }
 
+  @SuppressWarnings("unchecked")
+  public CommonToHttpServletRequest(Map<String, String> pathParams, Map<String, List<String>> queryParams,
+      Map<String, List<String>> httpHeaders, Object bodyObject, boolean isFormData) {
+    this(pathParams, queryParams, httpHeaders, bodyObject, isFormData, null);
+  }
+
   @Override
   public String getContentType() {
     return getHeader(HttpHeaders.CONTENT_TYPE);
@@ -164,8 +180,12 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
   }
 
   @Override
-  public Part getPart(String name) throws IOException, ServletException {
+  public Part getPart(String name) {
     Object value = findPartInputValue(name);
+    return getSinglePart(name, value);
+  }
+
+  private Part getSinglePart(String name, Object value) {
     if (value == null) {
       return null;
     }
@@ -196,6 +216,43 @@ public class CommonToHttpServletRequest extends AbstractHttpServletRequest {
             value.getClass().getName()));
   }
 
+  @Override
+  public Collection<Part> getParts() {
+    @SuppressWarnings("unchecked")
+    Map<String, Object> form = (Map<String, Object>) getAttribute(RestConst.FORM_PARAMETERS);
+    List<Part> partList = new ArrayList<>();
+    filePartListWithForm(partList, form);
+    return partList;
+  }
+
+  private void filePartListWithForm(List<Part> partList, Map<String, Object> form) {
+
+    for (String key : fileKeys) {
+      Object value = form.get(key);
+      if (value == null) {
+        continue;
+      }
+      Class<?> aClass = value.getClass();
+      System.out.println(aClass.isArray());
+      if (Collection.class.isInstance(value)) {
+        Collection<?> collection = (Collection<?>) value;
+        if (collection.isEmpty()) {
+          continue;
+        }
+        for (Object part : collection) {
+          partList.add(getSinglePart(key, part));
+        }
+      } else if (value.getClass().isArray()) {
+        Object[] params = (Object[]) value;
+        for (int i = 0; i < params.length; i++) {
+          partList.add(getSinglePart(key, params[i]));
+        }
+      } else {
+        partList.add(getSinglePart(key, value));
+      }
+    }
+  }
+
   protected Object findPartInputValue(String name) {
     @SuppressWarnings("unchecked")
     Map<String, Object> form = (Map<String, Object>) getAttribute(RestConst.FORM_PARAMETERS);
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
index 6f33883..cc6d6d1 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java
@@ -206,7 +206,8 @@ public class CseClientHttpRequest implements ClientHttpRequest {
 
   protected Object[] collectArguments() {
     HttpServletRequest mockRequest = new CommonToHttpServletRequest(requestMeta.getPathParams(), queryParams,
-        httpHeaders, requestBody, requestMeta.getSwaggerRestOperation().isFormData());
+        httpHeaders, requestBody, requestMeta.getSwaggerRestOperation().isFormData(),
+        requestMeta.getSwaggerRestOperation().getParamList());
     return RestCodec.restToArgs(mockRequest, requestMeta.getSwaggerRestOperation());
   }
 }
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
index 789cdca..1b32f27 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/CommonParameterTypeProcessor.java
@@ -17,9 +17,12 @@
 
 package org.apache.servicecomb.swagger.generator.core;
 
+
+import java.lang.reflect.Type;
+
 /**
  * 通过spi自动注入到所有的GeneratorContext实例中去
  */
 public interface CommonParameterTypeProcessor extends ParameterTypeProcessor {
-  Class<?> getParameterType();
+  Type getParameterType();
 }
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
index 8c9a834..3e3e3d9 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java
@@ -19,6 +19,7 @@ package org.apache.servicecomb.swagger.generator.core;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -37,6 +38,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.StringUtils;
 
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
 import io.swagger.models.HttpMethod;
 import io.swagger.models.Operation;
 import io.swagger.models.Path;
@@ -347,6 +350,26 @@ public class OperationGenerator {
     if (processor != null) {
       processor.process(this, paramIdx);
     }
+    Type realType = checkAndGetType(parameterType);
+    if (realType != null) {
+      processor = context.findParameterTypeProcessor(realType);
+      if (processor != null) {
+        processor.process(this, paramIdx);
+      }
+    }
+  }
+
+  // if is list<?> check and get respond type
+  private Type checkAndGetType(Type type) {
+    if (ParameterizedType.class.isAssignableFrom(type.getClass())) {
+      ParameterizedType targetType = (ParameterizedType) type;
+      Class<?> targetCls = (Class<?>) targetType.getRawType();
+      if (List.class.isAssignableFrom(targetCls)) {
+        return TypeFactory.defaultInstance().constructCollectionType(List.class,
+            (Class<?>) targetType.getActualTypeArguments()[0]);
+      }
+    }
+    return null;
   }
 
   public void correctOperation() {
diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
index 3b5bb0d..731d2db 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/processor/parametertype/HttpServletRequestProcessor.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.swagger.generator.core.processor.parametertype;
 
+import java.lang.reflect.Type;
+
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.servicecomb.swagger.extend.parameter.HttpRequestParameter;
@@ -25,7 +27,7 @@ import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
 
 public class HttpServletRequestProcessor implements CommonParameterTypeProcessor {
   @Override
-  public Class<?> getParameterType() {
+  public Type getParameterType() {
     return HttpServletRequest.class;
   }
 
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
similarity index 80%
copy from swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
copy to swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
index 12e4974..4b040c0 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileArrayTypeProcessor.java
@@ -17,25 +17,30 @@
 
 package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
 
+import java.lang.reflect.Type;
+
 import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
 import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
 import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
 import org.springframework.web.multipart.MultipartFile;
 
 import io.swagger.models.parameters.FormParameter;
+import io.swagger.models.properties.ArrayProperty;
 import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
 
-public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
+public class MultipartFileArrayTypeProcessor implements CommonParameterTypeProcessor {
   @Override
-  public Class<?> getParameterType() {
-    return MultipartFile.class;
+  public Type getParameterType() {
+    return MultipartFile[].class;
   }
 
   @Override
   public void process(OperationGenerator operationGenerator, int paramIdx) {
     FormParameter parameter = new FormParameter();
-    parameter.setType(new FileProperty().getType());
     parameter.setName(ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx));
+    Property property = new ArrayProperty(new FileProperty());
+    parameter.setProperty(property);
     operationGenerator.addProviderParameter(parameter);
   }
 }
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
similarity index 75%
copy from swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
copy to swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
index 12e4974..3be6a40 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileListTypeProcessor.java
@@ -17,25 +17,34 @@
 
 package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
 
+import java.lang.reflect.Type;
+import java.util.List;
+
 import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
 import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
 import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
 import org.springframework.web.multipart.MultipartFile;
 
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
 import io.swagger.models.parameters.FormParameter;
+import io.swagger.models.properties.ArrayProperty;
 import io.swagger.models.properties.FileProperty;
+import io.swagger.models.properties.Property;
+
+public class MultipartFileListTypeProcessor implements CommonParameterTypeProcessor {
 
-public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
   @Override
-  public Class<?> getParameterType() {
-    return MultipartFile.class;
+  public Type getParameterType() {
+    return TypeFactory.defaultInstance().constructCollectionType(List.class, MultipartFile.class);
   }
 
   @Override
   public void process(OperationGenerator operationGenerator, int paramIdx) {
     FormParameter parameter = new FormParameter();
-    parameter.setType(new FileProperty().getType());
     parameter.setName(ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx));
+    Property property = new ArrayProperty(new FileProperty());
+    parameter.setProperty(property);
     operationGenerator.addProviderParameter(parameter);
   }
 }
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
index 12e4974..8d0972d 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
+++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/MultipartFileTypeProcessor.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter;
 
+import java.lang.reflect.Type;
+
 import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
 import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
 import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
@@ -27,7 +29,7 @@ import io.swagger.models.properties.FileProperty;
 
 public class MultipartFileTypeProcessor implements CommonParameterTypeProcessor {
   @Override
-  public Class<?> getParameterType() {
+  public Type getParameterType() {
     return MultipartFile.class;
   }
 
diff --git a/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor b/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
index 0bad0b7..bea33a1 100644
--- a/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
+++ b/swagger/swagger-generator/generator-springmvc/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor
@@ -16,3 +16,5 @@
 #
 
 org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileTypeProcessor
+org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileArrayTypeProcessor
+org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.MultipartFileListTypeProcessor
\ No newline at end of file
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
index 94b8570..c0d5e31 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/converter/ConverterMgr.java
@@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
 @Component
 public class ConverterMgr {
   private static final Logger LOGGER = LoggerFactory.getLogger(ConverterMgr.class);
@@ -99,13 +101,40 @@ public class ConverterMgr {
     return findCommonConverter(target);
   }
 
+
   protected Converter findSrcTarget(Type src, Type target) {
     Map<Type, Converter> map = srcTargetMap.get(src);
-    if (map == null) {
-      return null;
+    if (map != null) {
+      return map.get(target);
+    }
+    Type realSrc = checkAndGetType(src);
+    if (realSrc != null) {
+      Map<Type, Converter> typeConverterMap = srcTargetMap.get(realSrc);
+      if (typeConverterMap != null) {
+        Converter converter = typeConverterMap.get(target);
+        if (converter != null) {
+          return converter;
+        }
+        Type realTarget = checkAndGetType(target);
+        if (realTarget != null) {
+          return typeConverterMap.get(realTarget);
+        }
+      }
     }
+    return null;
+  }
 
-    return map.get(target);
+  // if is list<?> check and get respond type
+  private Type checkAndGetType(Type type) {
+    if (ParameterizedType.class.isAssignableFrom(type.getClass())) {
+      ParameterizedType targetType = (ParameterizedType) type;
+      Class<?> targetCls = (Class<?>) targetType.getRawType();
+      if (List.class.isAssignableFrom(targetCls)) {
+        return TypeFactory.defaultInstance().constructCollectionType(List.class,
+            (Class<?>) targetType.getActualTypeArguments()[0]);
+      }
+    }
+    return null;
   }
 
   protected Converter findCommonConverter(Type target) {
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
index c995a93..5b4467c 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/generator/InvocationContextProcessor.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.swagger.invocation.generator;
 
+import java.lang.reflect.Type;
+
 import org.apache.servicecomb.swagger.extend.parameter.InvocationContextParameter;
 import org.apache.servicecomb.swagger.generator.core.CommonParameterTypeProcessor;
 import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
@@ -24,7 +26,7 @@ import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
 
 public class InvocationContextProcessor implements CommonParameterTypeProcessor {
   @Override
-  public Class<?> getParameterType() {
+  public Type getParameterType() {
     return InvocationContext.class;
   }
 
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java
new file mode 100644
index 0000000..54072f0
--- /dev/null
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartArrayConverter.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.swagger.invocation.converter;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import javax.servlet.http.Part;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+
+@Component
+public class SpringMultipartArrayConverter implements CustomizedConverter {
+
+  @Override
+  public Type getSrcType() {
+    return TypeFactory.defaultInstance().constructCollectionType(List.class, Part.class);
+  }
+
+  @Override
+  public Type getTargetType() {
+    return MultipartFile[].class;
+  }
+
+  @Override
+  public Object convert(Object value) {
+    if (value == null) {
+      return null;
+    }
+    @SuppressWarnings("unchecked")
+    List<Part> partList = (List<Part>) value;
+    PartToMultipartFile[] partArray = new PartToMultipartFile[partList.size()];
+    for (int i = 0; i < partArray.length; i++) {
+      partArray[i] = new PartToMultipartFile(partList.get(i));
+    }
+    return partArray;
+  }
+}
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java
new file mode 100644
index 0000000..24236be
--- /dev/null
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/converter/SpringMultipartListConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.swagger.invocation.converter;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.Part;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+@Component
+public class SpringMultipartListConverter implements CustomizedConverter {
+
+
+  @Override
+  public Type getSrcType() {
+    return TypeFactory.defaultInstance().constructCollectionType(List.class, Part.class);
+  }
+
+  @Override
+  public Type getTargetType() {
+    return TypeFactory.defaultInstance().constructCollectionType(List.class, MultipartFile.class);
+  }
+
+  @Override
+  public Object convert(Object value) {
+    if (value == null) {
+      return null;
+    }
+    @SuppressWarnings("unchecked")
+    List<Part> partList = (List<Part>) value;
+    List<PartToMultipartFile> fileList = new ArrayList<>();
+    partList.forEach(part -> {
+      fileList.add(new PartToMultipartFile(part));
+    });
+    return fileList;
+  }
+}