Java生成PDF的若干坑

本文首發於我的微信公衆號《andyqian》,期待你的關注~

前言

  在工做中,有使用Java生成大量的電子憑證,也就是PDF。在以前的文章中,有寫過,如何經過Java生成PDF。這裏就再也不描述,寫這篇文章主要是爲了記錄我在使用Java生成PDF過程當中犯過的錯,以及踩過的坑。html

內存溢出

  以前在使用生成方案的時候,咱們一開始採用的是《PD4ML》框架,這個框架相對來講,要小巧些。可是不足的是:它是閉源的。在批量生成PDF文件時會出現內存泄漏的現象。往往咱們在分析內存緣由時,都止步於下面這一段代碼:java

   private void createPdfByHtml(String html, File file) throws Exception{
        FileOutputStream fos = new FileOutputStream(file);  
        ...
        // 這一行
        pd4ml.render(sr, fos, new URL("http://"), "UTF-8");  
    }

所以,咱們不得不考慮換一個第三方庫。目前咱們使用的是開源庫《iText》,到目前爲止,穩定運行。linux

(咱們在選擇第三方框架時,儘可能選擇開源的,由於在出現問題後,咱們能夠經過查看源代碼分析,甚至能夠修改源代碼進行優化)。程序員

字體路徑

  這個問題是這樣的,咱們生成PDF時爲了支持中文字體,須要經過路徑加載指定的字體文件。而開發環境是windows,測試/生產環境均是Linux。字體放在面試

/resources/fonts/

由於:windows

  1. 在Window下獲取Thread.currentThread()爲null值微信

  2. 在Linux下獲取this.class.getClass().getResource(「/「).getPath();獲取爲null值app

因此在加載路徑時,咱們經過下述方法進行了兼容處理。框架

 /**
     * 應用場景:
     * 1.在windows下,使用Thread.currentThread()獲取路徑時,出現空對象,致使不能使用
     * 2.在linux下,使用PdfUtils.class獲取路徑爲null,
     * 獲取字體路徑
     * @return
     */
    private static String getFontPath(){
        String path="";
        // 1. 生產環境路徑
        ClassLoader classLoader= Thread.currentThread().getContextClassLoader();
        URL url = (classLoader==null)?null:classLoader.getResource("/");
        String threadCurrentPath = (url==null)?"":url.getPath();
        // 2. 若是線程獲取爲null,則使用當前PdfUtils.class加載路徑
        if(StringUtil.isBlank(threadCurrentPath)){
             path = PdfUtils.class.getClass().getResource("/").getPath();
        }
        // 3.拼接字體路徑
        StringBuffer stringBuffer = new StringBuffer(path);
        stringBuffer.append("/fonts/SIMKAI.TTF");
        path = stringBuffer.toString();
        logger.info("getFontPath threadCurrentPath: {}  path: {}",threadCurrentPath,path);
        return path;
    }

從這個方面也能體現一個程序員常見現象。測試

  1. 爲何在我電腦上是好的,在你電腦上不行,必定是你電腦問題。

  2. 不該該啊,在我電腦上是好的。絕對沒問題。

小夥伴們知道這個梗嗎?   這也是不少程序員使用與生產環境一致的系統做爲開發機的緣由。

字符編碼

  問題是這樣的。是因爲在生成PDF文件時轉換字節流時沒有指定編碼致使的: 以下所示:

public static void createdPdfByItextHtml(String htmlContent,File file){
            ...
            try {
                inputStream= new ByteArrayInputStream(htmlContent.getBytes());
                outputStream = new FileOutputStream(file);
            ...

此處省略掉部分代碼,此時htmlContent.getBytes()該方法沒有指定默認的編碼致使的。具體的緣由,在《初探JDK源碼之默認字符集》這篇文章中有詳細的描述。

生僻字

  這個坑是這樣的,由於正式公文的字體是:Kaiti_GB2312的字體。咱們使用過一段時間發現,Kaiti_GB2312字體對於生僻字支持不友好。
例如:

例如: 陳垚,經過Kaiti_GB2312生成後就只能顯示爲: 陳

其中字就沒有正常顯示。這個問題是很是嚴重的。若是電子憑證中姓名錯誤,也就表明,這份電子憑證是沒有法律效應的。在通過測試後,咱們最終使用了Kaiti字體,代替了Kaiti_GB2312。(這個坑,還真的不容易發現)

5. 枚舉傳遞類型
  若是Dubbo接口中使用枚舉做爲參數時,若是隻有單方面更新枚舉的值。會致使序列化出錯的問題。也就是說:

  1. 調用方在更新枚舉後,若是接受方不一樣時更新枚舉,會致使接收方的參數爲空對象。

注意點:  

  1. Dubbo服務調用之間,儘可能不要使用enum類型。

嚴格的HTML語義標籤

  使用過itext的朋友應該知道。HTML內容是須要嚴格的標籤的。也就是說,一個開始標籤,對應一個結束標籤。若是不匹配,則會生成失敗。(像<img src="www.baidu.com"/>) 標籤除外。

錯誤的html:

<html>
  <head>
     <title></title>
  </head>
  <body>
    <span>Hello World!</span></span>
  </body>
</html>

上面這段html內容,就多了一個</span>結束標籤,就屬於非嚴格的HTM語義標籤。

執行後,就會有下面這類錯誤:

com.itextpdf.tool.xml.exceptions.RuntimeWorkerException: Invalid nested tag span found, expected closing tag body.
    at com.itextpdf.tool.xml.XMLWorker.endElement(XMLWorker.java:134)
    at com.itextpdf.tool.xml.parser.XMLParser.endElement(XMLParser.java:396)

最後

  有些系統,看起來容易。作起來可並不是易事。在實現期間可能會遇到各類各樣的奇葩問題。但偏偏,就是這些怪問題。給咱們積累了經驗。我一直以爲,在寫代碼方面,是一個「倒黴」的人,由於各類奇怪的問題,都能讓我遇到。

公衆號內回覆:【PDF】,便可獲取項目。記得更換字體哦!

相關閱讀:

這裏寫圖片描述

 掃碼關注,一塊兒進步

我的博客: http://www.andyqian.com

相關文章
相關標籤/搜索