Spring MVC上傳文件原理和resolveLazily說明


問題:使用Spring MVC上傳大文件,發現從頁面提交,到進入后台controller,時間很長。懷疑是文件上傳完成后,才進入。由於在HTTP首部自定義了“Token”字段用於權限校驗,Token的有效時間很短,因此上傳大文件時,就會驗證Token失敗。

示例代碼:

前端:

<form action="upload" enctype="multipart/form-data" method="post">
        <table>
            <tr>
                <td>文件描述:</td>
                <td><input type="text" name="description"></td>
            </tr>
            <tr>
                <td>請選擇文件:</td>
                <td><input type="file" name="file"></td>
            </tr>
            <tr>
                <td><input type="submit" value="上傳"></td>
            </tr>
        </table>
</form>

 

controller:

@RequestMapping(value="/upload",method=RequestMethod.POST)
public String upload(HttpServletRequest request, 
        @RequestParam("description") String description, 
        @RequestParam("file") MultipartFile file) throws Exception {
    System.out.println("enter controller."); // 文件上傳完才打印
    }

springmvc-config.xml配置:

  <bean id="multipartResolver"  
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
        <property name="maxUploadSize">  
            <value>524288000</value>  
        </property>  
        <property name="defaultEncoding">
            <value>UTF-8</value>
        </property>
    </bean>

Spring MVC上傳文件使用了Commons FileUpload類庫,即CommonsMultipartResolver使用commons Fileupload來處理 multipart請求,將Commons FileUpload的對象轉換成了Spring MVC的對象。

那如果直接使用Commons FileUpload來進行文件上傳下載呢?

示例代碼:

protected void doPost(HttpServletRequest request,
                    HttpServletResponse response) throws ServletException, IOException {
                        System.out.println("enter servlet"); // 頁面提交后,立馬打印
              // 省略獲取上傳文件的邏輯
              // ...
}

發現從頁面提交,很快就進入了servlet。

既然Spring也是使用了Commons FileUpload類庫,但為什么差別這么大呢?Spring在轉換過程中做了什么其他操作呢?

查看源碼,或者直接看([Java] SpringMVC工作原理之四:MultipartResolver

發現有個resolveLazily參數是判斷是否要延遲解析文件(通過XML可以設置)。當 resolveLazily為false(默認)時,會立即調用 parseRequest() 方法對請求數據進行解析,然后將解析結果封裝到 DefaultMultipartHttpServletRequest中;而當resolveLazily為 true時,會在DefaultMultipartHttpServletRequest的initializeMultipart()方法調用parseRequest()方法對請求數據進行解析,而initializeMultipart()方法又是被getMultipartFiles()方法調用,即當需要獲取文件信息時才會去解析請求數據,這種方式用了懶加載的思想。

再次修改代碼:

1、在springmvc-config.xml增加一個配置resolveLazily,設置true(如何配置見文末);

2、將controller方法中將@RequestParam("file") MultipartFile file注釋

 

再次測試,發現還是不對。。增加打印日志后發現:

@RequestMapping(value="/upload",method=RequestMethod.POST)
public String upload(HttpServletRequest request, 
        @RequestParam("description") String description 
        /*@RequestParam("file") MultipartFile file*/) throws Exception {
    System.out.println("enter controller.");   // 還是文件上傳完后,才打印
    System.out.println(request.getHeader("Accept"));
    System.out.println(request.getParam("description"));
// 省略獲取上傳文件的邏輯 }

再注釋@RequestParam("description") String description

@RequestMapping(value="/upload",method=RequestMethod.POST)
public String upload(HttpServletRequest request
        /*@RequestParam("description") String description, */
        /*@RequestParam("file") MultipartFile file*/) throws Exception {
    System.out.println("enter controller.");   // 頁面提交后,立刻打印
    System.out.println(request.getHeader("Accept")); // 幾乎也是立刻打印
    System.out.println(request.getParam("description")); // 文件上傳完,才打印
// 省略獲取上傳文件的邏輯
}

這次,第1,2條日志很快打印,第3條日志仍是文件上傳完后才打印。簡單分析下,應該是后台一直在接收數據,HTTP首部很快就能獲取到並解析出來(首部和正文之間有一個空行),但請求正文部分必須在請求數據完全接收后才能解析,因此線程一直阻塞直到數據接收完成才打印第3條日志。因此解決方案是,把controller函數中的@RequestParam("description") String description也注釋掉,這樣頁面提交后,會立即進入controller中。

 

總結下最終的解決方案:

1、springmvc-config.xml增加一項配置:

<property name="resolveLazily ">
  <value>true</value>
</property>

2、在controller的方法參數中,不能附加請求參數,應在函數中自己解析參數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM