首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Java 導出 CSV 檔案 response.getOutputStream() 空指針異常排查

     編輯:程式設計疑難解答

Servlet/Controller 中導出 CSV 檔案時,response.getOutputStream() 拋出 NullPointerException。本文分析常見原因並提供標準導出範本。

核心原因:response 物件為 null 或已提交

原因說明驗證/修正
非 Servlet 環境呼叫在普通 Java 類別、單元測試、主執行緒中直接呼叫導出邏輯確保在 doGet/doPost、@Controller 方法、Filter 等 Web 容器管理的執行緒中執行
回應已提交在此之前已呼叫 response.getWriter()、forward()、sendRedirect()、或輸出超過緩衝區導致自動 flush檢查前置邏輯;導出前勿輸出任何內容;設定 response.setBufferSize(65536)
框架攔截/包裝Spring MVC ResponseBodyAdvice、Interceptor、Shiro/權限過濾器提前提交回應在導出方法上 @ResponseBody 並返回 void;或配置排除路徑
非標準 response 實作測試用 MockHttpServletResponse 未正確初始化 OutputStream使用 MockMvc 整合測試而非單元測試

標準 CSV 導出範本

@GetMapping("/export.csv")
public void exportCsv(HttpServletResponse response) throws IOException {
    // 1. 設置回應頭(必須在 getOutputStream() 之前)
    response.setContentType("text/csv; charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment; filename=\"export_" + LocalDate.now() + ".csv\"");
    response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Expires", "0");
    // 防止中文檔名亂碼 (RFC 5987)
    String encodedFilename = URLEncoder.encode("導出_" + LocalDate.now() + ".csv", StandardCharsets.UTF_8)
        .replaceAll("\\+", "%20");
    response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);

    // 2. 獲取輸出流
    try (ServletOutputStream out = response.getOutputStream();
         PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
        
        // 3. 寫入 BOM (UTF-8 with BOM) 讓 Excel 正確識別編碼
        out.write(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});
        
        // 4. 寫入 CSV 內容
        writer.println("姓名,年齡,城市");
        writer.println("張三,25,北京");
        writer.println("李四,30,上海");
        // ... 從資料庫/服務查詢數據循環寫入
        
        writer.flush(); // 關鍵:確保緩衝區寫入 OutputStream
    }
    // try-with-resources 自動關閉流,提交回應
}

關鍵注意點

  1. Header 先於 Stream:所有 setHeader/setContentType 必須在 getOutputStream() / getWriter() 之前完成。
  2. 單一流原則:同一請求只能呼叫 其中一個**getOutputStream() 或 getWriter(),二擇一,否則 IllegalStateException。
  3. 編碼一致性:OutputStreamWriter(..., UTF-8) 與 response.setCharacterEncoding("UTF-8") 保持一致。
  4. 大數據流式寫入:避免一次性載入所有資料到記憶體,邊查詢邊 writer.println()。
  5. 異常處理:若導出過程拋異常,回應可能已提交 Header,無法再傳錯誤頁,需在全域異常處理器記錄日誌。

Spring MVC 簡化寫法

@GetMapping("/export.csv")
public ResponseEntity<byte[]> exportCsv() {
    String csv = generateCsv(); // 產生 CSV 字串
    byte[] bytes = ("\uFEFF" + csv).getBytes(StandardCharsets.UTF_8); // 加 BOM
    
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
    headers.setContentDispositionFormData("attachment", "export_" + LocalDate.now() + ".csv");
    headers.setCacheControl("no-cache, no-store, must-revalidate");
    
    return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}

適用於中小數據量(< 10MB),大數據量仍建議 StreamingResponseBody 或 Servlet 直接寫流。

快速自檢清單

  • [ ] 方法參數包含 HttpServletResponse response?
  • [ ] 無前置 response.getWriter() / forward / redirect?
  • [ ] Header 設置在 getOutputStream() 前?
  • [ ] try-with-resources 管理流?
  • [ ] 測試環境用 MockMvc 而非手造 MockHttpServletResponse?

遵循「Header 先行、單一流、流式寫入、編碼統一」四原則,CSV 導出穩如磐石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved