spring上传文件

本文将说明spring上传文件如何配置,以及从request请求中解析到文件流的原理

#添加依赖

主要用来解析request请求流,获取文件字段名、上传文件名、content-type、headers等内容组装成FileItem

        <!--添加fileupload依赖-->
        <dependency>
            <groupid>commons-fileupload</groupid>
            <artifactid>commons-fileupload</artifactid>
            <version>1.3.3</version>
        </dependency>

#构建单例bean

CommonsMultipartResolver,将request请求从类型HttpServletRequest转化成MultipartHttpServletRequest,从MultipartHttpServletRequest可以获取上传文件的各种信息文件名、文件流等内容

注意:该bean的beanName要写成multipartResolver,否则无法获取到该bean

@Bean
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
    // &#x4E0A;&#x4F20;&#x9650;&#x5236;&#x6700;&#x5927;&#x5B57;&#x8282;&#x6570; -1&#x8868;&#x793A;&#x6CA1;&#x9650;&#x5236;
    commonsMultipartResolver.setMaxUploadSize(-1);
    // &#x6BCF;&#x4E2A;&#x6587;&#x4EF6;&#x9650;&#x5236;&#x6700;&#x5927;&#x5B57;&#x8282;&#x6570; -1&#x8868;&#x793A;&#x6CA1;&#x9650;&#x5236;
    commonsMultipartResolver.setMaxUploadSizePerFile(-1);
    commonsMultipartResolver.setDefaultEncoding(StandardCharsets.UTF_8.name());
    return commonsMultipartResolver;
}

#DispatcherServlet
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                    "': no multipart request handling provided");
        }
    }
}

#校验请求

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // multipartResolver&#x4E0D;&#x4E3A;&#x7A7A; &#x4E14; request&#x8BF7;&#x6C42;&#x5934;&#x4E2D;&#x7684;content-type&#x4EE5;multipart/&#x5F00;&#x5934;
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                         "this typically results from an additional MultipartFilter in web.xml");
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                         "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // &#x89E3;&#x6790;&#x8BF7;&#x6C42;
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.

    return request;
}

#解析请求

@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
    Assert.notNull(request, "Request must not be null");
    MultipartParsingResult parsingResult = parseRequest(request);
    return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
                                                  parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
    String encoding = determineEncoding(request);
    // &#x83B7;&#x53D6;FileUpload&#x5B9E;&#x4F8B;
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
        // &#x5C06;request&#x8BF7;&#x6C42;&#x89E3;&#x6790;&#x6210;FileItem
        List<fileitem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
        return parseFileItems(fileItems, encoding);
    }
    catch (FileUploadBase.SizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
    }
    catch (FileUploadBase.FileSizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
    }
    catch (FileUploadException ex) {
        throw new MultipartException("Failed to parse multipart servlet request", ex);
    }
}

// ctx->&#x5C06;request&#x8FDB;&#x884C;&#x4E86;&#x5305;&#x88C5;
public List<fileitem> parseRequest(RequestContext ctx)
    throws FileUploadException {
  List<fileitem> items = new ArrayList<fileitem>();
  boolean successful = false;
  try {
    // &#x901A;&#x8FC7;ctx&#x6784;&#x5EFA;FileItem&#x6D41;&#x7684;&#x8FED;&#x4EE3;&#x5668;
    FileItemIterator iter = getItemIterator(ctx);
    // FileItemFactory&#x521B;&#x5EFA;FileItem&#x7684;&#x5DE5;&#x5382;&#x5BF9;&#x8C61;
    FileItemFactory fac = getFileItemFactory();
    if (fac == null) {
      throw new NullPointerException("No FileItemFactory has been set.");
    }
    // &#x5224;&#x65AD;&#x662F;&#x5426;itemValid&#x662F;&#x5426;&#x4E3A;true&#xFF0C;&#x662F;&#x5426;&#x6709;&#x53EF;&#x8BFB;&#x6587;&#x4EF6;
    while (iter.hasNext()) {
      final FileItemStream item = iter.next();
      // Don't use getName() here to prevent an InvalidFileNameException.

      // &#x6587;&#x4EF6;&#x540D;&#x79F0;
      final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
      // &#x6784;&#x5EFA;FileItem
      FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
          item.isFormField(), fileName);
      items.add(fileItem);
      try {
        // &#x5C06;FileItemStreamImpl&#x6D41;&#x62F7;&#x8D1D;&#x5230;fileItem&#x7684;&#x8F93;&#x51FA;&#x6D41;&#x4E2D;&#xFF08;&#x7CFB;&#x7EDF;&#x4F1A;&#x81EA;&#x5EFA;&#x6587;&#x4EF6;&#xFF09;
        Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
      } catch (FileUploadIOException e) {
        throw (FileUploadException) e.getCause();
      } catch (IOException e) {
        throw new IOFileUploadException(format("Processing of %s request failed. %s",
            MULTIPART_FORM_DATA, e.getMessage()), e);
      }
      final FileItemHeaders fih = item.getHeaders();
      fileItem.setHeaders(fih);
    }
    successful = true;
    return items;
  } catch (FileUploadIOException e) {
    throw (FileUploadException) e.getCause();
  } catch (IOException e) {
    throw new FileUploadException(e.getMessage(), e);
  } finally {
    if (!successful) {
      for (FileItem fileItem : items) {
        try {
          fileItem.delete();
        } catch (Throwable e) {
          // ignore it
        }
      }
    }
  }
}

#&#x89E3;&#x6790;&#x83B7;&#x53D6;&#x5230;&#x7684;fileItems
protected MultipartParsingResult parseFileItems(List<fileitem> fileItems, String encoding) {
  MultiValueMap<string, multipartfile> multipartFiles = new LinkedMultiValueMap<>();
  Map<string, string[]> multipartParameters = new HashMap<>();
  Map<string, string> multipartParameterContentTypes = new HashMap<>();

  // Extract multipart files and multipart parameters.

  for (FileItem fileItem : fileItems) {
    // &#x662F;&#x5426;&#x662F;&#x8868;&#x5355;&#x5B57;&#x6BB5;&#xFF08;&#x4E0B;&#x9762;&#x7684;&#x89E3;&#x6790;&#x53EF;&#x4EE5;&#x770B;&#x5230;&#x6784;&#x5EFA;&#x65F6;&#x8BE5;&#x5B57;&#x6BB5;&#x4F20;&#x53C2; fileName == null&#xFF09;&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x6587;&#x4EF6;&#x540D;&#x662F;&#x5426;&#x4E3A;&#x7A7A;
    if (fileItem.isFormField()) {
      String value;
      String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
      try {
        value = fileItem.getString(partEncoding);
      }
      catch (UnsupportedEncodingException ex) {
        if (logger.isWarnEnabled()) {
          logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
              "' with encoding '" + partEncoding + "': using platform default");
        }
        value = fileItem.getString();
      }
      String[] curParam = multipartParameters.get(fileItem.getFieldName());
      if (curParam == null) {
        // simple form field
        multipartParameters.put(fileItem.getFieldName(), new String[] {value});
      }
      else {
        // array of simple form fields
        String[] newParam = StringUtils.addStringToArray(curParam, value);
        multipartParameters.put(fileItem.getFieldName(), newParam);
      }
      multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
    }
    else {
      // multipart file field &#x6784;&#x5EFA;MultipartFile
      CommonsMultipartFile file = createMultipartFile(fileItem);
      // &#x4EE5;&#x6587;&#x4EF6;&#x5B57;&#x6BB5;&#x540D;&#x4E3A;key &#xFF08;files&#xFF09;
      multipartFiles.add(file.getName(), file);
      if (logger.isDebugEnabled()) {
        logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
            " bytes with original filename [" + file.getOriginalFilename() + "], stored " +
            file.getStorageDescription());
      }
    }
  }
  return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}</string,></string,></string,></fileitem></fileitem></fileitem></fileitem></fileitem>

主要逻辑是这行代码 FileItemIterator iter = getItemIterator(ctx);,FileItem流迭代器的构造

#&#x6784;&#x9020;&#x65B9;&#x6CD5;
FileItemIteratorImpl(RequestContext ctx)
    throws FileUploadException, IOException {
  if (ctx == null) {
    throw new NullPointerException("ctx parameter");
  }

  // &#x83B7;&#x53D6;request&#x7684;content-type&#xFF0C;&#x9700;&#x8981;&#x4EE5;multipart/ &#x5F00;&#x5934;
  String contentType = ctx.getContentType();
  if ((null == contentType)
      || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
    throw new InvalidContentTypeException(
        format("the request doesn't contain a %s or %s stream, content type header is %s",
            MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
  }

  // &#x83B7;&#x53D6;request&#x7684;&#x8F93;&#x5165;&#x6D41;
  InputStream input = ctx.getInputStream();

  // &#x83B7;&#x53D6;&#x5185;&#x5BB9;&#x957F;&#x5EA6; content-length &#x4ECE;request&#x4E2D;&#x53D6;
  @SuppressWarnings("deprecation") // still has to be backward compatible
  final int contentLengthInt = ctx.getContentLength();

  // &#x901A;&#x8FC7;request.getHeader()&#x53D6;
  final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
      // Inline conditional is OK here CHECKSTYLE:OFF
      ? ((UploadContext) ctx).contentLength()
      : contentLengthInt;
  // CHECKSTYLE:ON

  // sizeMax&#x9650;&#x5236;&#x6D41;&#x5927;&#x5C0F; -1&#x5219;&#x4E0D;&#x9650;&#x5236;
  if (sizeMax >= 0) {
    if (requestSize != -1 && requestSize > sizeMax) {
      throw new SizeLimitExceededException(
          format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
              Long.valueOf(requestSize), Long.valueOf(sizeMax)),
          requestSize, sizeMax);
    }
    input = new LimitedInputStream(input, sizeMax) {
      @Override
      protected void raiseError(long pSizeMax, long pCount)
          throws IOException {
        FileUploadException ex = new SizeLimitExceededException(
            format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
                Long.valueOf(pCount), Long.valueOf(pSizeMax)),
            pCount, pSizeMax);
        throw new FileUploadIOException(ex);
      }
    };
  }

  // &#x83B7;&#x53D6;&#x5B57;&#x7B26;&#x7F16;&#x7801;
  String charEncoding = headerEncoding;
  if (charEncoding == null) {
    charEncoding = ctx.getCharacterEncoding();
  }

  // &#x901A;&#x8FC7;content-type = multipart/form-data; boundary=--------------------------205940049223747054037567
  // &#x83B7;&#x53D6;boundary&#x7684;&#x503C;&#x5206;&#x9694;&#x7B26;&#xFF08;&#x4E00;&#x4E32;&#x968F;&#x673A;&#x5B57;&#x7B26;&#xFF1F;&#xFF09;&#x5E76;&#x8F6C;&#x5316;&#x4E3A;&#x5B57;&#x8282;&#x6570;&#x7EC4;
  boundary = getBoundary(contentType);
  if (boundary == null) {
    throw new FileUploadException("the request was rejected because no multipart boundary was found");
  }

  // &#x8FDB;&#x5EA6;&#x66F4;&#x65B0;&#x5668;
  notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
  try {
    // &#x6784;&#x5EFA;&#x591A;&#x5143;&#x6D41;
    multi = new MultipartStream(input, boundary, notifier);
  } catch (IllegalArgumentException iae) {
    throw new InvalidContentTypeException(
        format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
  }
  // &#x8BBE;&#x7F6E;&#x8BF7;&#x6C42;&#x5934;&#x7F16;&#x7801;
  multi.setHeaderEncoding(charEncoding);

  // &#x8DF3;&#x8FC7;&#x5E8F;&#x8A00;
  skipPreamble = true;
  // &#x5F00;&#x59CB;&#x627E;&#x7B2C;&#x4E00;&#x4E2A;&#x6587;&#x4EF6;&#x9879;&#x76EE;
  findNextItem();
}

接着再来看下MultipartStream的构建

#MultipartStream&#x6784;&#x9020;
public MultipartStream(InputStream input,  // request&#x8F93;&#x5165;&#x6D41;
    byte[] boundary,  // &#x8FB9;&#x754C; &#x5B57;&#x8282;&#x6570;&#x7EC4;
    int bufSize, // &#x7F13;&#x51B2;&#x533A;&#x5927;&#x5C0F; &#x9ED8;&#x8BA4;4096
    ProgressNotifier pNotifier) {

  if (boundary == null) {
    throw new IllegalArgumentException("boundary may not be null");
  }
  // We prepend CR/LF to the boundary to chop trailing CR/LF from
  // body-data tokens.   CR &#x56DE;&#x8F66;\r LF &#x6362;&#x884C;\n
  // protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
  this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
  // &#x7F13;&#x51B2;&#x533A;&#x5927;&#x5C0F;&#x5224;&#x65AD;
  if (bufSize < this.boundaryLength + 1) {
    throw new IllegalArgumentException(
        "The buffer size specified for the MultipartStream is too small");
  }

  this.input = input;
  // &#x91CD;&#x65B0;&#x786E;&#x5B9A;&#x7F13;&#x51B2;&#x533A;&#x5927;&#x5C0F;
  this.bufSize = Math.max(bufSize, boundaryLength * 2);
  // &#x521B;&#x5EFA;&#x7F13;&#x51B2;&#x533A; &#x7528;&#x6765;&#x4ECE;&#x8BFB;inputStream &#x63A5;&#x53D7;&#x6570;&#x636E;
  this.buffer = new byte[this.bufSize];
  this.notifier = pNotifier;

  // &#x8FB9;&#x754C;&#x6570;&#x7EC4;
  this.boundary = new byte[this.boundaryLength];
  this.keepRegion = this.boundary.length;

  // &#x5C06;BOUNDARY_PREFIX&#x6570;&#x7EC4;&#x548C;&#x5165;&#x53C2;boundary&#x6570;&#x7EC4;&#x7684;&#x5185;&#x5BB9;&#x6309;&#x5E8F;&#x590D;&#x5236;&#x5230;&#x65B0;&#x7684;boundary&#x4E2D;
  System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
      BOUNDARY_PREFIX.length);
  System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
      boundary.length);

  // head&#x548C;tail&#x4E3A;&#x7F13;&#x51B2;&#x533A;&#x64CD;&#x4F5C;&#x7684;&#x7D22;&#x5F15;
  // 0 <= 0 head < bufsize tail="0;" }< code></=>

接着看findNextItem方法,找第一个文件项目

/**
 * Called for finding the next item, if any.

 *
 * @return True, if an next item was found, otherwise false.

 * @throws IOException An I/O error occurred.

 */
private boolean findNextItem() throws IOException {
  if (eof) {
    return false;
  }
  // &#x5F00;&#x59CB;&#x4E3A;null
  if (currentItem != null) {
    currentItem.close();
    currentItem = null;
  }
  for (;;) {
    boolean nextPart;
    if (skipPreamble) {
      // &#x4E22;&#x5F03;&#x76F4;&#x5230;&#x8FB9;&#x754C;&#x5206;&#x9694;&#x7B26;&#x7684;&#x6240;&#x6709;&#x6570;&#x636E; &#x518D;&#x8BFB;&#x53D6;&#x8FB9;&#x754C;
      nextPart = multi.skipPreamble();
    } else {
      // &#x76F4;&#x63A5;&#x8BFB;&#x53D6;&#x8FB9;&#x754C;
      nextPart = multi.readBoundary();
    }
    if (!nextPart) {
      if (currentFieldName == null) {
        // Outer multipart terminated -> No more data
        eof = true;
        return false;
      }
      // Inner multipart terminated -> Return to parsing the outer
      multi.setBoundary(boundary);
      currentFieldName = null;
      continue;
    }
    // &#x89E3;&#x6790;&#x5934;&#x90E8; multi.readHeaders()&#x4ECE;&#x7F13;&#x51B2;&#x533A;&#x89E3;&#x6790;&#x5230;&#x6240;&#x6709;&#x8BF7;&#x6C42;&#x5934;&#x7684;&#x5B57;&#x7B26;&#x4E32;
    // &#x63A5;&#x7740;getParsedHeaders&#x5C06;&#x5B57;&#x7B26;&#x4E32;&#x6309;\r\n&#x5206;&#x9694;&#xFF0C;&#x56E0;&#x4E3A;&#x6BCF;&#x4E00;&#x884C;&#x6570;&#x636E;&#x90FD;&#x662F;&#x4E00;&#x4E2A;&#x8BF7;&#x6C42;&#x5934;&#x5185;&#x5BB9;
    FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
    if (currentFieldName == null) {
      // We're parsing the outer multipart
      // &#x83B7;&#x53D6;&#x4E0A;&#x4F20;&#x6587;&#x4EF6;&#x5B57;&#x6BB5;&#x53C2;&#x6570;&#x540D;name = files
      String fieldName = getFieldName(headers);
      if (fieldName != null) {
        String subContentType = headers.getHeader(CONTENT_TYPE);  // img/jpeg
        if (subContentType != null
            &&  subContentType.toLowerCase(Locale.ENGLISH)
            .startsWith(MULTIPART_MIXED)) {
          currentFieldName = fieldName;
          // Multiple files associated with this field name
          byte[] subBoundary = getBoundary(subContentType);
          multi.setBoundary(subBoundary);
          skipPreamble = true;
          continue;
        }
        // &#x6587;&#x4EF6;&#x540D; IMG_0908.JPG
        String fileName = getFileName(headers);
        // &#x6839;&#x636E;&#x5B57;&#x6BB5;&#x540D;&#x3001;&#x6587;&#x4EF6;&#x540D;&#x3001;&#x8BF7;&#x6C42;&#x5934;&#x7B49;&#x6784;&#x5EFA;FileItemStream&#x5BF9;&#x8C61;
        currentItem = new FileItemStreamImpl(fileName,
            fieldName, headers.getHeader(CONTENT_TYPE),
            fileName == null, getContentLength(headers));
        // &#x8BBE;&#x7F6E;&#x8BF7;&#x6C42;&#x5934;
        currentItem.setHeaders(headers);
        // ++items
        notifier.noteItem();
        // &#x5F53;&#x671F;&#x6709;&#x53EF;&#x7528;item
        itemValid = true;
        return true;
      }
    } else {
      String fileName = getFileName(headers);
      if (fileName != null) {
        currentItem = new FileItemStreamImpl(fileName,
            currentFieldName,
            headers.getHeader(CONTENT_TYPE),
            false, getContentLength(headers));
        currentItem.setHeaders(headers);
        notifier.noteItem();
        itemValid = true;
        return true;
      }
    }
    multi.discardBodyData();
  }
}

下图为缓冲区数据,首行为boundary分隔符内容也就是上文提到的—-加一串计算出来的随机字符,\r\n后接着为content-Disposition和content-type请求头,可以看到 HEADER_SEPARATOR头部分隔符\r\n\r\n 和分隔符之前的为请求头数据

另外boundary字节数组对应的内容为————————–031262929361076583805179,下图的首行内容比其多两个-

spring上传文件

下图为解析完后的头部

spring上传文件

接下来再看下FileItemStreamImpl的构造过程,比较简单

#FileItemStreamImpl&#x6784;&#x9020;&#x65B9;&#x6CD5;
FileItemStreamImpl(String pName, String pFieldName,
    String pContentType, boolean pFormField,
    long pContentLength) throws IOException {
  name = pName;
  fieldName = pFieldName;
  contentType = pContentType;
  formField = pFormField;
  // &#x521B;&#x5EFA;itemStream&#x6D41; &#x672C;&#x8D28;&#x4E0A;&#x662F;&#x4ECE;request&#x7684;inputstream&#x83B7;&#x53D6;&#x6570;&#x636E;
  // &#x4ECE;head&#x4F4D;&#x7F6E;&#x518D;&#x5F00;&#x59CB;&#x627E;boundary&#x8FB9;&#x754C;&#x5206;&#x9694;&#x7B26;&#xFF0C;&#x82E5;&#x627E;&#x5230;&#x5C06;&#x8FB9;&#x754C;&#x7684;&#x524D;&#x4E00;&#x4E2A;&#x7D22;&#x5F15;&#x8D4B;&#x503C;&#x7ED9;pos&#x53D8;&#x91CF;&#xFF0C;&#x5E76;&#x4E14;&#x5F53;&#x524D;&#x6587;&#x4EF6;&#x53EF;&#x8BFB;&#x5B57;&#x7B26;&#x6570;&#x4E3A;pos - head
  final ItemInputStream itemStream = multi.newInputStream();
  InputStream istream = itemStream;
  // &#x82E5;&#x6587;&#x4EF6;&#x5927;&#x5C0F;&#x9650;&#x5236;&#xFF0C;&#x8D85;&#x51FA;&#x957F;&#x5EA6;&#x4F1A;&#x629B;&#x51FA;&#x5F02;&#x5E38;
  if (fileSizeMax != -1) {
    if (pContentLength != -1
        &&  pContentLength > fileSizeMax) {
      FileSizeLimitExceededException e =
          new FileSizeLimitExceededException(
              format("The field %s exceeds its maximum permitted size of %s bytes.",
                  fieldName, Long.valueOf(fileSizeMax)),
              pContentLength, fileSizeMax);
      e.setFileName(pName);
      e.setFieldName(pFieldName);
      throw new FileUploadIOException(e);
    }
    istream = new LimitedInputStream(istream, fileSizeMax) {
      @Override
      protected void raiseError(long pSizeMax, long pCount)
          throws IOException {
        itemStream.close(true);
        FileSizeLimitExceededException e =
            new FileSizeLimitExceededException(
                format("The field %s exceeds its maximum permitted size of %s bytes.",
                    fieldName, Long.valueOf(pSizeMax)),
                pCount, pSizeMax);
        e.setFieldName(fieldName);
        e.setFileName(name);
        throw new FileUploadIOException(e);
      }
    };
  }
  stream = istream;
}

再来看看ItemInputStream,上面的FileItemStreamImpl对象有这个类型参数,主要用来获取请求流的,因为ItemInputStream类是MultipartStream的内部类,能够调用MultipartStream中的input流。

ItemInputStream

public class ItemInputStream extends InputStream implements Closeable {

        // &#x76EE;&#x524D;&#x5DF2;&#x7ECF;&#x8BFB;&#x53D6;&#x7684;&#x5B57;&#x8282;&#x6570;
        private long total;

        // &#x5FC5;&#x987B;&#x4FDD;&#x6301;&#x7684;&#x5B57;&#x8282;&#x6570;&#x53EF;&#x80FD;&#x662F;&#x5206;&#x9694;&#x7B26;boundary&#x7684;&#x4E00;&#x90E8;&#x5206;
        private int pad;

        // &#x7F13;&#x51B2;&#x533A;&#x7684;&#x5F53;&#x524D;&#x504F;&#x79FB;
        private int pos;

        // stream&#x6D41;&#x662F;&#x5426;&#x5173;&#x95ED;
        private boolean closed;

        // &#x6784;&#x9020;&#x65B9;&#x6CD5;
        ItemInputStream() {
            findSeparator();
        }

        // &#x5BFB;&#x627E;&#x8FB9;&#x754C;&#x5206;&#x9694;&#x7B26;boundary&#x7684;&#x524D;&#x4E00;&#x4E2A;&#x7D22;&#x5F15;
        private void findSeparator() {
            pos = MultipartStream.this.findSeparator();
            if (pos == -1) {
                if (tail - head > keepRegion) {
                    pad = keepRegion;
                } else {
                    pad = tail - head;
                }
            }
        }

        // &#x53EF;&#x8BFB;&#x53D6;&#x5B57;&#x8282;&#x6570;
        @Override
        public int available() throws IOException {
            // &#x53EF;&#x8BFB;=&#x5C3E;-&#x9996;-&#x8FB9;&#x754C;&#x957F;&#x5EA6;
            if (pos == -1) {
                return tail - head - pad;
            }
            // pos !=-1 &#x8BF4;&#x660E;pos&#x540E;&#x9762;&#x662F;&#x8FB9;&#x754C;&#x4E86; &#x53EA;&#x80FD;&#x8BFB;&#x5230;&#x8FD9;&#x4E2A;&#x8FB9;&#x754C;&#x4E4B;&#x524D;&#x7684;&#x6570;&#x636E;
            // &#x53EF;&#x8BFB; = &#x8FB9;&#x754C;&#x524D;&#x7684;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x7D22;&#x5F15; - &#x9996;
            return pos - head;
        }

        private static final int BYTE_POSITIVE_OFFSET = 256;

        // &#x8BFB;&#x53D6;stream&#x6D41;&#x7684;&#x4E0B;&#x4E00;&#x4E2A;&#x5B57;&#x7B26;
        @Override
        public int read() throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            if (available() == 0 && makeAvailable() == 0) {
                return -1;
            }
            ++total;
            int b = buffer[head++];
            if (b >= 0) {
                return b;
            }
            // &#x5982;&#x679C;&#x8D1F;&#x7684; &#x52A0;&#x4E0A;256
            return b + BYTE_POSITIVE_OFFSET;
        }

        // &#x8BFB;&#x53D6;&#x5B57;&#x8282;&#x5230;&#x7ED9;&#x5B9A;&#x7684;&#x7F13;&#x51B2;&#x533A;b&#x4E2D;
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            if (len == 0) {
                return 0;
            }
            int res = available();
            if (res == 0) {
                res = makeAvailable();
                if (res == 0) {
                    return -1;
                }
            }
            res = Math.min(res, len);
            System.arraycopy(buffer, head, b, off, res);
            // head&#x52A0;&#x504F;&#x79FB;
            head += res;
            total += res;
            return res;
        }

        // &#x5173;&#x95ED;&#x8F93;&#x5165;&#x6D41;
        @Override
        public void close() throws IOException {
            close(false);
        }

        /**
         * Closes the input stream.

         *
         * @param pCloseUnderlying Whether to close the underlying stream
         *   (hard close)
         * @throws IOException An I/O error occurred.

         */
        public void close(boolean pCloseUnderlying) throws IOException {
            if (closed) {
                return;
            }
            if (pCloseUnderlying) {
                closed = true;
                input.close();
            } else {
                for (;;) {
                    int av = available();
                    if (av == 0) {
                        av = makeAvailable();
                        if (av == 0) {
                            break;
                        }
                    }
                    skip(av);
                }
            }
            closed = true;
        }

        // &#x8DF3;&#x8FC7;&#x7F13;&#x51B2;&#x533A;&#x4E2D;&#x7ED9;&#x5B9A;&#x957F;&#x5EA6;&#x7684;&#x5B57;&#x8282;
        @Override
        public long skip(long bytes) throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            int av = available();
            if (av == 0) {
                av = makeAvailable();
                if (av == 0) {
                    return 0;
                }
            }
            long res = Math.min(av, bytes);
            head += res;
            return res;
        }

        // &#x8BD5;&#x56FE;&#x8BFB;&#x53D6;&#x66F4;&#x591A;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x8FD4;&#x56DE;&#x53EF;&#x8BFB;&#x5B57;&#x8282;&#x6570;
        private int makeAvailable() throws IOException {
            if (pos != -1) {
                return 0;
            }

            // &#x5C06;&#x6570;&#x636E;&#x79FB;&#x5230;&#x7F13;&#x51B2;&#x533A;&#x7684;&#x5F00;&#x5934;&#xFF0C;&#x820D;&#x5F03;&#x8FB9;&#x754C;
            total += tail - head - pad;
            System.arraycopy(buffer, tail - pad, buffer, 0, pad);

            // Refill buffer with new data.

            head = 0;
            tail = pad;

            for (;;) {
                // &#x8BFB;&#x53D6;tail&#x4F4D;&#x7F6E;&#x5F00;&#x59CB;&#x8BFB; bufSize-tail&#x957F;&#x5EA6;&#x7684;&#x5B57;&#x8282;&#x5230;buffer&#x7F13;&#x51B2;&#x533A;&#x4E2D;
                int bytesRead = input.read(buffer, tail, bufSize - tail);
                if (bytesRead == -1) {
                    // The last pad amount is left in the buffer.

                    // Boundary can't be in there so signal an error
                    // condition.

                    final String msg = "Stream ended unexpectedly";
                    throw new MalformedStreamException(msg);
                }
                if (notifier != null) {
                    notifier.noteBytesRead(bytesRead);
                }
                // tail&#x52A0;&#x504F;&#x79FB;
                tail += bytesRead;

                // &#x518D;&#x5C1D;&#x8BD5;&#x627E;boundary&#x8FB9;&#x754C;&#xFF0C;&#x8D4B;&#x503C;pos -1
                findSeparator();

                int av = available();

                // &#x8FD4;&#x56DE;&#x53EF;&#x8BFB;&#x5B57;&#x8282;&#x6570;
                if (av > 0 || pos != -1) {
                    return av;
                }
            }
        }

        // &#x5224;&#x65AD;&#x6D41;&#x662F;&#x5426;&#x5173;&#x95ED;
        public boolean isClosed() {
            return closed;
        }

    }

#接受请求

请求解析完成后就可以以文件对象接收了,参数类型为MultipartFile,可强转为CommonsMultipartFile,参数名需要与上传文件的fieldName相对应或者也可以用@RequestParam注解指定参数名

@RequestMapping(value = "file/upload", method = RequestMethod.POST)
@ResponseBody
public Object uploadFile(MultipartFile[] files, HttpServletRequest request, HttpServletResponse response) throws IOException {
  ....... &#x4E0A;&#x4F20;&#x903B;&#x8F91;
  return CommonResult.succ("&#x4E0A;&#x4F20;&#x6210;&#x529F;");
}

可以用postman测试,content-type的boundary显示是请求发送时计算,不知道怎么算的反正是一串随机数,是用来分隔多个文件内容的,在源码中可以看到不能缺失否则解析过程中会报错

spring上传文件

spring上传文件

Original: https://www.cnblogs.com/monianxd/p/16550065.html
Author: 默念x
Title: spring上传文件

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/590107/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球