欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring實(shí)現(xiàn)文件上傳的配置詳解

 更新時(shí)間:2022年08月05日 08:21:45   作者:默念x  
這篇文章將為大家詳細(xì)說明一下spring上傳文件如何配置,以及從request請(qǐng)求中解析到文件流的原理,文中示例代碼講解詳細(xì),感興趣的可以了解一下

添加依賴

主要用來解析request請(qǐng)求流,獲取文件字段名、上傳文件名、content-type、headers等內(nèi)容組裝成FileItem

        <!--添加fileupload依賴-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

構(gòu)建單例bean

CommonsMultipartResolver,將request請(qǐng)求從類型HttpServletRequest轉(zhuǎn)化成MultipartHttpServletRequest,從MultipartHttpServletRequest可以獲取上傳文件的各種信息文件名、文件流等內(nèi)容 

注意:該bean的beanName要寫成multipartResolver,否則無法獲取到該bean

@Bean
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
    // 上傳限制最大字節(jié)數(shù) -1表示沒限制
    commonsMultipartResolver.setMaxUploadSize(-1);
    // 每個(gè)文件限制最大字節(jié)數(shù) -1表示沒限制
    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");
		}
	}
}

校驗(yàn)請(qǐng)求

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // multipartResolver不為空 且 request請(qǐng)求頭中的content-type以multipart/開頭
    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 {
                // 解析請(qǐng)求
                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;
}

解析請(qǐng)求

@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);
    // 獲取FileUpload實(shí)例
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
        // 將request請(qǐng)求解析成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->將request進(jìn)行了包裝
public List<FileItem> parseRequest(RequestContext ctx)
    throws FileUploadException {
  List<FileItem> items = new ArrayList<FileItem>();
  boolean successful = false;
  try {
    // 通過ctx構(gòu)建FileItem流的迭代器
    FileItemIterator iter = getItemIterator(ctx);
    // FileItemFactory創(chuàng)建FileItem的工廠對(duì)象
    FileItemFactory fac = getFileItemFactory();
    if (fac == null) {
      throw new NullPointerException("No FileItemFactory has been set.");
    }
    // 判斷是否itemValid是否為true,是否有可讀文件
    while (iter.hasNext()) {
      final FileItemStream item = iter.next();
      // Don't use getName() here to prevent an InvalidFileNameException.
      // 文件名稱
      final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
      // 構(gòu)建FileItem
      FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
          item.isFormField(), fileName);
      items.add(fileItem);
      try {
        // 將FileItemStreamImpl流拷貝到fileItem的輸出流中(系統(tǒng)會(huì)自建文件)
        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
        }
      }
    }
  }
}


#解析獲取到的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) {
    // 是否是表單字段(下面的解析可以看到構(gòu)建時(shí)該字段傳參 fileName == null),也就是文件名是否為空
    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 構(gòu)建MultipartFile
      CommonsMultipartFile file = createMultipartFile(fileItem);
      // 以文件字段名為key (files)
      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);
}

主要邏輯是這行代碼FileItemIterator iter = getItemIterator(ctx);,F(xiàn)ileItem流迭代器的構(gòu)造  

#構(gòu)造方法
FileItemIteratorImpl(RequestContext ctx)
    throws FileUploadException, IOException {
  if (ctx == null) {
    throw new NullPointerException("ctx parameter");
  }

  // 獲取request的content-type,需要以multipart/ 開頭
  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));
  }

  // 獲取request的輸入流
  InputStream input = ctx.getInputStream();

  // 獲取內(nèi)容長(zhǎng)度 content-length 從request中取
  @SuppressWarnings("deprecation") // still has to be backward compatible
  final int contentLengthInt = ctx.getContentLength();

  // 通過request.getHeader()取
  final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
      // Inline conditional is OK here CHECKSTYLE:OFF
      ? ((UploadContext) ctx).contentLength()
      : contentLengthInt;
  // CHECKSTYLE:ON

  // sizeMax限制流大小 -1則不限制
  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);
      }
    };
  }

  // 獲取字符編碼
  String charEncoding = headerEncoding;
  if (charEncoding == null) {
    charEncoding = ctx.getCharacterEncoding();
  }

  // 通過content-type = multipart/form-data; boundary=--------------------------205940049223747054037567
  // 獲取boundary的值分隔符(一串隨機(jī)字符?)并轉(zhuǎn)化為字節(jié)數(shù)組
  boundary = getBoundary(contentType);
  if (boundary == null) {
    throw new FileUploadException("the request was rejected because no multipart boundary was found");
  }

  // 進(jìn)度更新器
  notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
  try {
    // 構(gòu)建多元流
    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);
  }
  // 設(shè)置請(qǐng)求頭編碼
  multi.setHeaderEncoding(charEncoding);

  // 跳過序言
  skipPreamble = true;
  // 開始找第一個(gè)文件項(xiàng)目
  findNextItem();
}

接著再來看下MultipartStream的構(gòu)建

#MultipartStream構(gòu)造
public MultipartStream(InputStream input,  // request輸入流
    byte[] boundary,  // 邊界 字節(jié)數(shù)組
    int bufSize, // 緩沖區(qū)大小 默認(rèn)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 回車\r LF 換行\(zhòng)n
  // protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
  this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
  // 緩沖區(qū)大小判斷 
  if (bufSize < this.boundaryLength + 1) {
    throw new IllegalArgumentException(
        "The buffer size specified for the MultipartStream is too small");
  }

  this.input = input;
  // 重新確定緩沖區(qū)大小 
  this.bufSize = Math.max(bufSize, boundaryLength * 2);
  // 創(chuàng)建緩沖區(qū) 用來從讀inputStream 接受數(shù)據(jù)
  this.buffer = new byte[this.bufSize];
  this.notifier = pNotifier;

  // 邊界數(shù)組
  this.boundary = new byte[this.boundaryLength];
  this.keepRegion = this.boundary.length;

  // 將BOUNDARY_PREFIX數(shù)組和入?yún)oundary數(shù)組的內(nèi)容按序復(fù)制到新的boundary中
  System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
      BOUNDARY_PREFIX.length);
  System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
      boundary.length);

  // head和tail為緩沖區(qū)操作的索引
  // 0 <= head < bufSize 
  // 0 <= tail <= bufSize
  head = 0;
  tail = 0;
}

接著看findNextItem方法,找第一個(gè)文件項(xiàng)目

/**
 * 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;
  }
  // 開始為null
  if (currentItem != null) {
    currentItem.close();
    currentItem = null;
  }
  for (;;) {
    boolean nextPart;
    if (skipPreamble) {
      // 丟棄直到邊界分隔符的所有數(shù)據(jù) 再讀取邊界
      nextPart = multi.skipPreamble();
    } else {
      // 直接讀取邊界
      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;
    }
    // 解析頭部 multi.readHeaders()從緩沖區(qū)解析到所有請(qǐng)求頭的字符串 
    // 接著getParsedHeaders將字符串按\r\n分隔,因?yàn)槊恳恍袛?shù)據(jù)都是一個(gè)請(qǐng)求頭內(nèi)容
    FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
    if (currentFieldName == null) {
      // We're parsing the outer multipart
      // 獲取上傳文件字段參數(shù)名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;
        }
        // 文件名 IMG_0908.JPG
        String fileName = getFileName(headers);
        // 根據(jù)字段名、文件名、請(qǐng)求頭等構(gòu)建FileItemStream對(duì)象
        currentItem = new FileItemStreamImpl(fileName,
            fieldName, headers.getHeader(CONTENT_TYPE),
            fileName == null, getContentLength(headers));
        // 設(shè)置請(qǐng)求頭 
        currentItem.setHeaders(headers);
        // ++items
        notifier.noteItem();
        // 當(dāng)期有可用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();
  }
}

下圖為緩沖區(qū)數(shù)據(jù),首行為boundary分隔符內(nèi)容也就是上文提到的----加一串計(jì)算出來的隨機(jī)字符,\r\n后接著為content-Disposition和content-type請(qǐng)求頭,可以看到 HEADER_SEPARATOR頭部分隔符\r\n\r\n 和分隔符之前的為請(qǐng)求頭數(shù)據(jù)

另外boundary字節(jié)數(shù)組對(duì)應(yīng)的內(nèi)容為--------------------------031262929361076583805179,下圖的首行內(nèi)容比其多兩個(gè)-

下圖為解析完后的頭部

接下來再看下FileItemStreamImpl的構(gòu)造過程,比較簡(jiǎn)單 

#FileItemStreamImpl構(gòu)造方法
FileItemStreamImpl(String pName, String pFieldName,
    String pContentType, boolean pFormField,
    long pContentLength) throws IOException {
  name = pName;
  fieldName = pFieldName;
  contentType = pContentType;
  formField = pFormField;
  // 創(chuàng)建itemStream流 本質(zhì)上是從request的inputstream獲取數(shù)據(jù)
  // 從head位置再開始找boundary邊界分隔符,若找到將邊界的前一個(gè)索引賦值給pos變量,并且當(dāng)前文件可讀字符數(shù)為pos - head
  final ItemInputStream itemStream = multi.newInputStream();
  InputStream istream = itemStream;
  // 若文件大小限制,超出長(zhǎng)度會(huì)拋出異常
  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對(duì)象有這個(gè)類型參數(shù),主要用來獲取請(qǐng)求流的,因?yàn)镮temInputStream類是MultipartStream的內(nèi)部類,能夠調(diào)用MultipartStream中的input流。

ItemInputStream

public class ItemInputStream extends InputStream implements Closeable {

        // 目前已經(jīng)讀取的字節(jié)數(shù)
        private long total;

        // 必須保持的字節(jié)數(shù)可能是分隔符boundary的一部分 
        private int pad;

        // 緩沖區(qū)的當(dāng)前偏移
        private int pos;

        // stream流是否關(guān)閉 
        private boolean closed;

        // 構(gòu)造方法
        ItemInputStream() {
            findSeparator();
        }

        // 尋找邊界分隔符boundary的前一個(gè)索引
        private void findSeparator() {
            pos = MultipartStream.this.findSeparator();
            if (pos == -1) {
                if (tail - head > keepRegion) {
                    pad = keepRegion;
                } else {
                    pad = tail - head;
                }
            }
        }

        // 可讀取字節(jié)數(shù) 
        @Override
        public int available() throws IOException {
            // 可讀=尾-首-邊界長(zhǎng)度
            if (pos == -1) {
                return tail - head - pad;
            }
            // pos !=-1 說明pos后面是邊界了 只能讀到這個(gè)邊界之前的數(shù)據(jù) 
            // 可讀 = 邊界前的最后一個(gè)索引 - 首
            return pos - head;
        }

        private static final int BYTE_POSITIVE_OFFSET = 256;

        // 讀取stream流的下一個(gè)字符
        @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;
            }
            // 如果負(fù)的 加上256
            return b + BYTE_POSITIVE_OFFSET;
        }

        // 讀取字節(jié)到給定的緩沖區(qū)b中
        @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加偏移
            head += res;
            total += res;
            return res;
        }

        // 關(guān)閉輸入流
        @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;
        }

        // 跳過緩沖區(qū)中給定長(zhǎng)度的字節(jié)
        @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;
        }

        // 試圖讀取更多的數(shù)據(jù),返回可讀字節(jié)數(shù)
        private int makeAvailable() throws IOException {
            if (pos != -1) {
                return 0;
            }

            // 將數(shù)據(jù)移到緩沖區(qū)的開頭,舍棄邊界
            total += tail - head - pad;
            System.arraycopy(buffer, tail - pad, buffer, 0, pad);

            // Refill buffer with new data.
            head = 0;
            tail = pad;

            for (;;) {
                // 讀取tail位置開始讀 bufSize-tail長(zhǎng)度的字節(jié)到buffer緩沖區(qū)中 
                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加偏移 
                tail += bytesRead;

                // 再嘗試找boundary邊界,賦值pos -1
                findSeparator();
                
                int av = available();

                // 返回可讀字節(jié)數(shù) 
                if (av > 0 || pos != -1) {
                    return av;
                }
            }
        }

        // 判斷流是否關(guān)閉
        public boolean isClosed() {
            return closed;
        }

    }

接受請(qǐng)求

請(qǐng)求解析完成后就可以以文件對(duì)象接收了,參數(shù)類型為MultipartFile,可強(qiáng)轉(zhuǎn)為CommonsMultipartFile,參數(shù)名需要與上傳文件的fieldName相對(duì)應(yīng)或者也可以用@RequestParam注解指定參數(shù)名

@RequestMapping(value = "file/upload", method = RequestMethod.POST)
@ResponseBody
public Object uploadFile(MultipartFile[] files, HttpServletRequest request, HttpServletResponse response) throws IOException {
  ....... 上傳邏輯
  return CommonResult.succ("上傳成功");
}

可以用postman測(cè)試,content-type的boundary顯示是請(qǐng)求發(fā)送時(shí)計(jì)算,不知道怎么算的反正是一串隨機(jī)數(shù),是用來分隔多個(gè)文件內(nèi)容的,在源碼中可以看到不能缺失否則解析過程中會(huì)報(bào)錯(cuò)

以上就是Spring實(shí)現(xiàn)文件上傳的配置詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring文件上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 在springboot中使用攔截器的步驟詳解

    在springboot中使用攔截器的步驟詳解

    攔截器Interceptor,是SpringMVC中的核心內(nèi)容,在SpringBoot中使用Interceptor,同時(shí)采用全注解開發(fā),這篇文章主要介紹了在springboot中使用攔截器的步驟,需要的朋友可以參考下
    2022-01-01
  • IDEA 配置 JRebel 熱部署的方法(推薦)

    IDEA 配置 JRebel 熱部署的方法(推薦)

    這篇文章主要介紹了IDEA 配置 JRebel 熱部署的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • 深度解析Java中volatile的內(nèi)存語義實(shí)現(xiàn)以及運(yùn)用場(chǎng)景

    深度解析Java中volatile的內(nèi)存語義實(shí)現(xiàn)以及運(yùn)用場(chǎng)景

    這篇文章主要介紹了Java中volatile的內(nèi)存語義實(shí)現(xiàn)以及運(yùn)用場(chǎng)景,通過JVM的機(jī)制來分析volatile關(guān)鍵字在線程編程中的作用,需要的朋友可以參考下
    2015-12-12
  • SpringBoot項(xiàng)目的配置文件中設(shè)置server.port不生效問題

    SpringBoot項(xiàng)目的配置文件中設(shè)置server.port不生效問題

    這篇文章主要介紹了SpringBoot項(xiàng)目的配置文件中設(shè)置server.port不生效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • SpringBoot集成Beetl后統(tǒng)一處理頁(yè)面異常的方法

    SpringBoot集成Beetl后統(tǒng)一處理頁(yè)面異常的方法

    這篇文章主要介紹了SpringBoot集成Beetl后統(tǒng)一處理頁(yè)面異常的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • java中實(shí)現(xiàn)創(chuàng)建目錄與創(chuàng)建文件的操作實(shí)例

    java中實(shí)現(xiàn)創(chuàng)建目錄與創(chuàng)建文件的操作實(shí)例

    用Java創(chuàng)建文件或目錄非常簡(jiǎn)單,下面這篇文章主要給大家介紹了關(guān)于java中實(shí)現(xiàn)創(chuàng)建目錄與創(chuàng)建文件的操作實(shí)例,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Java注解的簡(jiǎn)單入門小案例

    Java注解的簡(jiǎn)單入門小案例

    這篇文章主要介紹了Java注解的簡(jiǎn)單入門小案例,注解是干什么的?怎么使用?注解的簡(jiǎn)單用法,需要的朋友可以參考下
    2023-04-04
  • Mybatis-Plus處理Mysql?Json類型字段的詳細(xì)教程

    Mybatis-Plus處理Mysql?Json類型字段的詳細(xì)教程

    這篇文章主要給大家介紹了關(guān)于Mybatis-Plus處理Mysql?Json類型字段的詳細(xì)教程,Mybatis-Plus可以很方便地處理JSON字段,在實(shí)體類中可以使用@JSONField注解來標(biāo)記JSON字段,同時(shí)在mapper.xml中使用json函數(shù)來操作JSON字段,需要的朋友可以參考下
    2024-01-01
  • 深入淺析Java中的final關(guān)鍵字

    深入淺析Java中的final關(guān)鍵字

    在Java中,final關(guān)鍵字可以用來修飾類、方法和變量(包括成員變量和局部變量),下面通過本篇文章給大家介紹java中的final關(guān)鍵字,對(duì)java fina關(guān)鍵字相關(guān)知識(shí)感興趣的朋友一起看看吧
    2015-12-12
  • Java實(shí)現(xiàn)多數(shù)據(jù)源的幾種方式總結(jié)

    Java實(shí)現(xiàn)多數(shù)據(jù)源的幾種方式總結(jié)

    這篇文章主要給大家總結(jié)介紹了關(guān)于Java實(shí)現(xiàn)多數(shù)據(jù)源的幾種方式,最近項(xiàng)目中的工作流需要查詢多個(gè)數(shù)據(jù)源的數(shù)據(jù),數(shù)據(jù)源可能是不同種類的,需要的朋友可以參考下
    2023-08-08

最新評(píng)論