[Java] SpringMVC工作原理之四:MultipartResolver
MultipartResolver 用于處理文件上傳,當收到請求時 DispatcherServlet 的?checkMultipart() 方法會調用 MultipartResolver 的?isMultipart() 方法判斷請求中是否包含文件。如果請求數據中包含文件,則調用?MultipartResolver 的?resolveMultipart()?方法對請求的數據進行解析,然后將文件數據解析成 MultipartFile 并封裝在 MultipartHttpServletRequest (繼承了?HttpServletRequest) 對象中,最后傳遞給 Controller,在?MultipartResolver 接口中有如下方法:
- boolean isMultipart(HttpServletRequest request);?// 是否是 multipart
- MultipartHttpServletRequest resolveMultipart(HttpServletRequest request);?// 解析請求
- void cleanupMultipart(MultipartHttpServletRequest request);
MultipartFile 封裝了請求數據中的文件,此時這個文件存儲在內存中或臨時的磁盤文件中,需要將其轉存到一個合適的位置,因為請求結束后臨時存儲將被清空。在 MultipartFile 接口中有如下方法:
- String getName();?// 獲取參數的名稱
- String getOriginalFilename();?// 獲取文件的原名稱
- String getContentType();?// 文件內容的類型
- boolean isEmpty();?// 文件是否為空
- long getSize();?// 文件大小
- byte[] getBytes();?// 將文件內容以字節數組的形式返回
- InputStream getInputStream();?// 將文件內容以輸入流的形式返回
- void transferTo(File dest);?// 將文件內容傳輸到指定文件中
MultipartResolver 是一個接口,它的實現類如下圖所示,分為?CommonsMultipartResolver 類和?StandardServletMultipartResolver 類。
其中 CommonsMultipartResolver 使用 commons Fileupload 來處理 multipart 請求,所以在使用時,必須要引入相應的 jar 包;而 StandardServletMultipartResolver 是基于 Servlet 3.0來處理 multipart 請求的,所以不需要引用其他 jar 包,但是必須使用支持 Servlet 3.0的容器才可以,以tomcat為例,從 Tomcat 7.0.x的版本開始就支持 Servlet 3.0了。
一、CommonsMultipartResolver
1 使用方式
1.1 配置文件
<!-- 定義文件上傳解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 設定默認編碼 --><property name="defaultEncoding" value="UTF-8"></property><!-- 設定文件上傳的最大值為5MB,5*1024*1024 --><property name="maxUploadSize" value="5242880"></property><!-- 設定文件上傳時寫入內存的最大值,如果小于這個參數不會生成臨時文件,默認為10240 --><property name="maxInMemorySize" value="40960"></property><!-- 上傳文件的臨時路徑 --><property name="uploadTempDir" value="fileUpload/temp"></property><!-- 延遲文件解析 --><property name="resolveLazily" value="true"/> </bean>1.2 上傳表單
要在 form 標簽中加入 enctype="multipart/form-data" 表示該表單要提交文件。
<form action="${pageContext.request.contextPath}/test/file-upload.do" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="提交"> </form>1.3 處理文件
@RequestMapping("/file-upload") public ModelAndView upload(@RequestParam(value = "file", required = false) MultipartFile file, HttpServletRequest request, HttpSession session) {// 文件不為空if(!file.isEmpty()) {// 文件存放路徑String path = request.getServletContext().getRealPath("/");// 文件名稱String name = String.valueOf(new Date().getTime()+"_"+file.getOriginalFilename());File destFile = new File(path,name);// 轉存文件try {file.transferTo(destFile);} catch (IllegalStateException | IOException e) {e.printStackTrace();}// 訪問的urlString url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()+ request.getContextPath() + "/" + name; } ModelAndView mv = new ModelAndView();mv.setViewName("other/home");return mv; }2 源碼分析
CommonsMultipartResolver 實現了?MultipartResolver 接口,resolveMultipart() 方法如下所示,其中 resolveLazily 是判斷是否要延遲解析文件(通過XML可以設置)。當?resolveLazily 為 flase 時,會立即調用 parseRequest() 方法對請求數據進行解析,然后將解析結果封裝到 DefaultMultipartHttpServletRequest 中;而當?resolveLazily 為 true 時,會在?DefaultMultipartHttpServletRequest 的?initializeMultipart() 方法調用?parseRequest() 方法對請求數據進行解析,而?initializeMultipart() 方法又是被?getMultipartFiles() 方法調用,即當需要獲取文件信息時才會去解析請求數據,這種方式用了懶加載的思想。
@Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {Assert.notNull(request, "Request must not be null");if (this.resolveLazily) {//懶加載,當調用DefaultMultipartHttpServletRequest的getMultipartFiles()方法時才解析請求數據return new DefaultMultipartHttpServletRequest(request) {@Override //當getMultipartFiles()方法被調用時,如果還未解析請求數據,則調用initializeMultipart()方法進行解析 protected void initializeMultipart() {MultipartParsingResult parsingResult = parseRequest(request);setMultipartFiles(parsingResult.getMultipartFiles());setMultipartParameters(parsingResult.getMultipartParameters());setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());}};} else {//立即解析請求數據,并將解析結果封裝到DefaultMultipartHttpServletRequest對象中MultipartParsingResult parsingResult = parseRequest(request);return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());} }在上面的代碼中可以看到,對請求數據的解析工作是在?parseRequest() 方法中進行的,繼續看一下 parseRequest() 方法源碼
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {// 獲取請求的編碼類型String encoding = determineEncoding(request);FileUpload fileUpload = prepareFileUpload(encoding);try {List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);return parseFileItems(fileItems, encoding);} catch (...) {} }在?parseRequest() 方法中,首先調用了?prepareFileUpload() 方法來根據編碼類型確定一個?FileUpload 實例,然后利用這個?FileUpload 實例解析請求數據后得到文件信息,最后將文件信息解析成?CommonsMultipartFile (實現了 MultipartFile 接口)?并包裝在?MultipartParsingResult 對象中。
?
二、StandardServletMultipartResolver
1 使用方式
1.1 配置文件
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>這里并沒有配置文件大小等參數,這些參數的配置在 web.xml 中
<servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup><multipart-config><!-- 臨時文件的目錄 --><location>d:/</location><!-- 上傳文件最大2M --><max-file-size>2097152</max-file-size><!-- 上傳文件整個請求不超過4M --><max-request-size>4194304</max-request-size></multipart-config> </servlet>1.2 上傳表單
要在 form 標簽中加入 enctype="multipart/form-data" 表示該表單要提交文件。
<form action="${pageContext.request.contextPath}/test/file-upload.do" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="提交"> </form>1.3 處理文件
1.3.1 通過 MultipartFile 類型的參數
@RequestMapping("/file-upload") public ModelAndView upload(@RequestParam(value = "file", required = false) MultipartFile file, HttpServletRequest request, HttpSession session) {// 文件不為空if(!file.isEmpty()) {// 文件存放路徑String path = request.getServletContext().getRealPath("/");// 文件名稱String name = String.valueOf(new Date().getTime()+"_"+file.getOriginalFilename());File destFile = new File(path,name);// 轉存文件try {file.transferTo(destFile);} catch (IllegalStateException | IOException e) {e.printStackTrace();}// 訪問的urlString url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()+ request.getContextPath() + "/" + name; } ModelAndView mv = new ModelAndView();mv.setViewName("other/home");return mv; }1.3.2 通過?MultipartHttpServletRequest 類型的參數
@RequestMapping("/file-upload") public ModelAndView upload(MultipartHttpServletRequest request, HttpSession session) {// 根據頁面input標簽的nameMultipartFile file = request.getFile("file");// 文件不為空if(!file.isEmpty()) {// 文件存放路徑String path = request.getServletContext().getRealPath("/");// 文件名稱String name = String.valueOf(new Date().getTime()+"_"+file.getOriginalFilename());File destFile = new File(path,name);// 轉存文件try {file.transferTo(destFile);} catch (IllegalStateException | IOException e) {e.printStackTrace();}// 訪問的urlString url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()+ request.getContextPath() + "/" + name; } ModelAndView mv = new ModelAndView();mv.setViewName("other/home");return mv; }2 源碼分析
StandardServletMultipartResolver?實現了?MultipartResolver 接口,resolveMultipart() 方法如下所示,其中 resolveLazily 是判斷是否要延遲解析文件(通過XML可以設置)。
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily); } public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {super(request);// 判斷是否立即解析if (!lazyParsing) {parseRequest(request);} }對請求數據的解析工作是在?parseRequest() 方法中進行的,繼續看一下 parseRequest() 方法源碼
private void parseRequest(HttpServletRequest request) {try {Collection<Part> parts = request.getParts();this.multipartParameterNames = new LinkedHashSet<String>(parts.size());MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());for (Part part : parts) {String disposition = part.getHeader(CONTENT_DISPOSITION);String filename = extractFilename(disposition);if (filename == null) {filename = extractFilenameWithCharset(disposition);}if (filename != null) {files.add(part.getName(), new StandardMultipartFile(part, filename));} else {this.multipartParameterNames.add(part.getName());}}setMultipartFiles(files);} catch (Throwable ex) {} }parseRequest() 方法利用了 servlet3.0 的?request.getParts() 方法獲取上傳文件,并將其封裝到?MultipartFile 對象中。
轉載于:https://www.cnblogs.com/liuys635/p/10248811.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的[Java] SpringMVC工作原理之四:MultipartResolver的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript是如何工作的:使用M
- 下一篇: 专访阿里云MVP王俊杰:开发者的超能力是