Spring POI 從模板生成Excel並打印

Spring POI 從模板生成Excel並打印


提醒:

  • java運行在服務器端,使用java調用打印時,外部連接服務器訪問打印程序,調用的是服務器端的打印機。

  • 如果想調用本地打印機可以使用JavaScript。

理想:

Created with Raphaël 2.1.0用戶用戶服務器服務器我要打印報表收到請求,調用打印程序弄好了,你打印吧調用打印機,打印報表

實際:

Created with Raphaël 2.1.0用戶用戶服務器服務器我要打印報表收到請求,調用打印程序怎麼調用了我自己的打印機,是java的鍋,我不背很抱歉,報表在我這打印好了,你過來取吧!你大爺的!

項目需求:從數據庫中生成報表並打印。之前已經寫好報表導出成Excel模塊,所以我想在此基礎上進行擴充,先生成Excel文件,再將該文件打印。由於本次項目使用Chrome,如果是IE瀏覽器可以使用自帶打印功能,但是Chrome並沒有。下面是解決過程:

Java自帶打印功能

在查詢資料工程中得知java自帶打印功能,但是不能打印office文檔,可以打印PDF。
在測試過程中發現PDF也打印不了,不知道是什麼原因。
雖然打印類型可選DocFlavor.INPUT_STREAM.PDF,或者自動識別DocFlavor.INPUT_STREAM.AUTOSENSE,但是打印PDF時還是會拋異常(使用PDF打印機測試):

Exception in thread "main" java.lang.IllegalArgumentException: services must be non-null and non-empty
    at javax.print.ServiceUI.printDialog(ServiceUI.java:167)
    at com.srie.util.test.TestPrinter.main(TestPrinter.java:32)

打印圖片正常,可以設置打印參數,代碼如下:(忘記是從哪位朋友博客裏看到的了o(╯□╰)o)

package com.srie.util.test;

import javax.print.*;
import javax.print.attribute.DocAttributeSet;
import javax.print.attribute.HashDocAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.swing.*;
import java.io.File;
import java.io.FileInputStream;

public class TestPrinter {
    public static void main(String[] args) {
        JFileChooser fileChooser = new JFileChooser(); // 創建打印作業
        int state = fileChooser.showOpenDialog(null);
        if (state == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile(); // 獲取選擇的文件
            // 構建打印請求屬性集
            HashPrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
            // 設置打印格式,打印JPG圖片
            DocFlavor flavor = DocFlavor.INPUT_STREAM.JPEG;
            // 查找所有的可用的打印服務
            PrintService printService[] = PrintServiceLookup
                    .lookupPrintServices(flavor, pras);
            // 定位默認的打印服務
            PrintService defaultService = PrintServiceLookup
                    .lookupDefaultPrintService();
            // 顯示打印對話框
            PrintService service = ServiceUI.printDialog(null, 200, 200,
                    printService, defaultService, flavor, pras);
            if (service != null) {
                try {
                    DocPrintJob job = service.createPrintJob(); // 創建打印作業
                    FileInputStream fis = new FileInputStream(file); // 構造待打印的文件流
                    DocAttributeSet das = new HashDocAttributeSet();
                    Doc doc = new SimpleDoc(fis, flavor, das);
                    job.print(doc, pras);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


Java打印功能

JACOB - Java COM Bridge

下載鏈接:http://download.csdn.net/detail/wangxiaoan1234/9909130
使用jacob可以方便的操作office文檔,甚至打印,而我的關注點在打印上。

1. 使用jacob首先要將jar包對應的dll文件放到“某個目錄”下。

有說放在Windows/system32下的,有說放在%JAVA_HOME/jre/bin%下的。經我測試得到的結果是Windows7系統只在%JAVA_HOME/jre/bin%放一份dll文件即可,Windows10不需要dll文件。
在使用過程中我遇到一個問題,同樣的代碼在兩臺電腦上一個打印成功,一個拋異常。

Windows7_64位系統:
- office2007
- eclipse_32位
- jdk1.8_32位
%JAVA_HOME/jre/bin%下放入×64dll文件後打印正常。

Windows10_64位系統:
- office2016
- eclipse_32位
- jdk1.8_32位
%JAVA_HOME/jre/bin%下放入dll文件後打印拋如下異常:

java.lang.NoSuchMethodError: com.jacob.com.Dispatch.put(Lcom/jacob/com/Dispatch;Ljava/lang/String;Ljava/lang/Object;)V

換成jdk1.8_64位後報錯如下:

java.lang.NoClassDefFoundError: Could not initialize class com.jacob.com.ComThread

原因:jar包對應的dll文件是32位,應該用64位,換成64位打印正常。

Windows10_64位系統:
- office2007
- IDEA2017.1
- jdk1.8_32位
添不添加dll文件打印時都會拋如下異常:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x77a282a5, pid=14440, tid=15272
#
# JRE version: Java(TM) SE Runtime Environment (8.0_31-b13) (build 1.8.0_31-b13)
# Java VM: Java HotSpot(TM) Client VM (25.31-b07 mixed mode windows-x86 )
# Problematic frame:
# C  [ntdll.dll+0x782a5]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# D:\tools\tomcat-7.0.57\bin\hs_err_pid14440.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

**Windows10_64位系統:
- office2007
- IDEA2017.1
- jdk1.8_64位
無需dll文件打印正常。**

在遇到拋異常的情況的時候,檢查代碼無錯,請確認JDK與IDE的版本,dll文件需和jar包對應,位數和電腦系統對應。

2. 在項目中添加jar包依賴。

由於項目採用maven管理,將jacob-1.18.jar放到WEB-INF/lib下,在pom.xml中添加如下代碼後maven更新項目。

<!-- 打印依賴的jar包 -->
<dependency>
    <groupId>com.jacob</groupId>
    <artifactId>jacob</artifactId>
    <version>1.18</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/jacob-1.18.jar</systemPath>
</dependency>

3. 代碼

首先感謝甲方公司的chykong大神,代碼是基於他寫的方法上完成的。

打印參數類:PrintsVo

package com.srie.common.vo;
/**
 * 打印頁面設置條件
 * @author 王曉安
 * @date 2017-7-11
 */
public class PrintVo {

    private final double rate = 0.393700787; //1釐米=0.393700787英寸

    private short zzxh; //紙張型號,如A4
    private boolean dyfx; //打印方向,true爲橫向,false爲豎向
    private double topMargin; //頁面上邊距
    private double bottomMargin; //頁面下邊距
    private double leftMargin; //頁面左邊距
    private double rightMargin; //頁面右邊距

    public short getZzxh() {
        return zzxh;
    }

    public void setZzxh(short zzxh) {
        this.zzxh = zzxh;
    }

    public boolean getDyfx() {
        return dyfx;
    }

    public void setDyfx(boolean dyfx) {
        this.dyfx = dyfx;
    }

    public double getTopMargin() {
        return topMargin;
    }

    public void setTopMargin(double topMargin) {
        this.topMargin = topMargin * rate;
    }

    public double getBottomMargin() {
        return bottomMargin;
    }

    public void setBottomMargin(double bottomMargin) {
        this.bottomMargin = bottomMargin * rate;
    }

    public double getLeftMargin() {
        return leftMargin;
    }

    public void setLeftMargin(double leftMargin) {
        this.leftMargin = leftMargin * rate;
    }

    public double getRightMargin() {
        return rightMargin;
    }

    public void setRightMargin(double rightMargin) {
        this.rightMargin = rightMargin * rate;
    }

    @Override
    public String toString() {
        return "PrintVo [zzxh=" + zzxh + ", dyfx=" + dyfx + ", topMargin=" + topMargin + ", bottomMargin="
                + bottomMargin + ", leftMargin=" + leftMargin + ", rightMargin=" + rightMargin + "]";
    }

}

打印方法類:Printer

package com.srie.util.test;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import com.srie.common.vo.PrintVo;
import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;

import java.io.*;

/**
 * 打印測試
 * @author 王曉安
 * 2017年7月16日
 */
public class Printer {

    //打印文件依賴的jacob-1.18.jar需要在jre路徑下放jacob-1.18-x86.dll文件
    static {
        FileOutputStream os = null;
        FileInputStream in = null;
        //獲取Windows版本,取得對應dll文件
        String systemBit = System.getProperties().get("os.arch").toString().substring(3); //amd64 or amd32
        String dllFile = "";
        if (systemBit.equals("64"))
            dllFile = "jacob-1.18-x64.dll";
        else
            dllFile = "jacob-1.18-x86.dll";
        //jre目錄下bin目錄,將web-inf下lib文件裏的dll文件放到這裏,打印需要這個文件
        File jreBinDll = new File(System.getProperty("java.home") + "/bin/" + dllFile);
        //打印所需的dll文件路徑(系統根目錄)
        File printDLL = new File(Thread.currentThread().getContextClassLoader().getResource("").getPath() + "../../" + dllFile);
        if (!jreBinDll.exists()) {
            try {
                in = new FileInputStream(printDLL);
                os = new FileOutputStream(jreBinDll);
                byte[] tempArr = new byte[(int) printDLL.length()];
                in.read(tempArr);
                os.write(tempArr);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 打印報表,後臺調取數據並生成Excel文件參考自exportRpt方法,
     * 不同之處是將生成的Excel以文件形式直接保存在本地,並調用系統默認打印機打印生成的Excel
     * @author 王曉安
     * @param data 需要轉換成Excel的二維數組
     * @param srcFile 報表模板
     * @param file_name 生成的Excel文件名
     * @param titleRows 標題欄行數
     * @param alignment Excel每列的對齊方式數組
     * @param printVo 打印參數
     */
    public static void print(String[][] data, String srcFile, String file_name, int titleRows, short[] alignment, PrintVo printVo) {
        FileOutputStream os = null;
        FileInputStream in = null;
        //臨時文件夾,用於保存將要打印的Excel文件
        File tempFolder= new File("D:/tempPrint");
        //Excel文件名
        String fileName = "temp.xls";
        //保存將要打印的excel
        File printFile = null;
        try {
            in = new FileInputStream(srcFile);
            Workbook work = new HSSFWorkbook(in);
            HSSFSheet sheet = (HSSFSheet) work.getSheetAt(0);
            sheet.getPrintSetup().setLandscape(printVo.getDyfx());// 打印方向,true:橫向,false:縱向(默認)
            sheet.setMargin(HSSFSheet.TopMargin, printVo.getTopMargin());// 頁邊距(上)
            sheet.setMargin(HSSFSheet.BottomMargin, printVo.getBottomMargin());// 頁邊距(下)
            sheet.setMargin(HSSFSheet.LeftMargin, printVo.getLeftMargin());// 頁邊距(左)
            sheet.setMargin(HSSFSheet.RightMargin, printVo.getRightMargin());// 頁邊距(右)
            sheet.setVerticallyCenter(false);//設置Excel是否垂直居中
            sheet.setHorizontallyCenter(false);//設置Excel是否水平居中
            //設置打印紙張
            HSSFPrintSetup ps = sheet.getPrintSetup();
            ps.setPaperSize(printVo.getZzxh());
            // 數據樣式
            Font fontData = work.createFont();
            fontData.setFontHeightInPoints((short) 11);
            fontData.setFontName("宋體");
            fontData.setBoldweight((short) 1);

            Font fontDataBold = work.createFont();
            fontDataBold.setFontHeightInPoints((short) 10);
            fontDataBold.setFontName("宋體");
            fontDataBold.setBoldweight(Font.BOLDWEIGHT_BOLD);
            CellStyle cellDataStyleBold = work.createCellStyle();
            cellDataStyleBold.setFont(fontDataBold);
            cellDataStyleBold.setBorderBottom((short) 1);
            cellDataStyleBold.setBorderLeft((short) 1);
            cellDataStyleBold.setBorderRight((short) 1);
            cellDataStyleBold.setBorderTop((short) 1);
            cellDataStyleBold.setFillPattern(CellStyle.SOLID_FOREGROUND);
            cellDataStyleBold.setFillForegroundColor(HSSFColor.WHITE.index);
            cellDataStyleBold.setAlignment(CellStyle.ALIGN_CENTER);
            cellDataStyleBold.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
            cellDataStyleBold.setWrapText(true);

            //填充單元格樣式
            CellStyle cellTextStyle = work.createCellStyle();
            cellTextStyle.setFont(fontData);
            cellTextStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
            cellTextStyle.setWrapText(true);

            // 得到行,並填充數據和表格樣式
            for (int i = 0; i < data.length; i++) {
                Row row = sheet.createRow(i + titleRows);// 創建新行
                for (int j = 0; j < data[i].length; j++) {
                    cellDataStyleBold.setAlignment(alignment[j]);
                    String str = data[i][j];
                    Cell cell = row.createCell(j);// 得到第j個單元格
                    cell.setCellStyle(cellDataStyleBold);// 填充樣式
                    cell.setCellValue(str);// 填充值
                }
            }
            //如果文件夾不存在,則創建該臨時文件夾
            if (!tempFolder.exists()) {
                tempFolder.mkdirs();
            }
            //如果文件夾還不存在,可能該用戶電腦沒有D盤,在C盤創建相應文件夾
            if (!tempFolder.exists()) {
                tempFolder = new File(tempFolder.getPath().replace('D', 'C'));
                tempFolder.mkdirs();
            }
            printFile = new File(tempFolder, fileName);
            os = new FileOutputStream(printFile);
            work.write(os);
            os.flush();
            work.close();
            //初始化COM線程
            ComThread.InitSTA();
            //新建Excel對象
            ActiveXComponent xl = new ActiveXComponent("Excel.Application");
            //設置打印時不打開文檔,true爲打開文檔
            Dispatch.put(xl, "Visible", new Variant(false));
            //打開具體的工作簿
            Dispatch workbooks = xl.getProperty("Workbooks").toDispatch();
            // 打開文檔
            Dispatch excel = Dispatch.call(workbooks, "Open", printFile.getPath()).toDispatch();
            // 開始打印
            Dispatch.call(excel, "PrintOut");
            xl.invoke("Quit", new Variant[] {});
        } catch (FileNotFoundException e) {
            System.out.println("文件路徑錯誤");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("文件輸入流錯誤");
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 釋放資源
            ComThread.Release();
        }
    }
}

打印測試類:TestPrinter

package com.srie.util.test;

import com.srie.common.vo.PrintVo;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.PrintSetup;

public class TestPrinter {
    public static void main(String[] args) {
        PrintVo printVo = new PrintVo(); //設置打印參數的類
        printVo.setTopMargin(1); //上邊距 1cm
        printVo.setBottomMargin(1); //下邊距 1cm
        printVo.setLeftMargin(1); //左邊距 1cm
        printVo.setRightMargin(1); //右邊距 1cm
        printVo.setDyfx(true); //打印方向,true橫向,false縱向
        printVo.setZzxh(PrintSetup.A4_PAPERSIZE); //打印紙張型號A4, /** A4 - 210x297 mm */short A4_PAPERSIZE = 9;
        short[] alignment = new short[]{CellStyle.ALIGN_CENTER, CellStyle.ALIGN_CENTER}; //short ALIGN_CENTER = 0x2;
        String srcFile = "D:/測試模板.xls";//定義報表模板
        String[][] data = new String[][]{{"Lisa", "22"}, {"Tony", "21"}};
        Printer printer = new Printer();
        String fileName = "測試";
        printer.print(data, srcFile, fileName, 1, alignment, printVo);
    }
}

測試結果:


測試結果hhhh

可以寫一個前臺頁面,用來傳遞打印參數和調取打印方法。


打印設置

發佈了31 篇原創文章 · 獲贊 60 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章