一,依賴jar包
<!-- freemarker 讀取html模板文件 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<!-- xml 將html模板文件轉換成pdf -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.0.9</version>
</dependency>
<!-- 多線程要用到 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
二,模板文件(爲html,命名後綴爲.ftl)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title></title>
<style>
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
font-family: SimSun;
}
section{
display:block;
margin: 20px 10px;
}
.title{
text-align: center;
}
.preface p{
line-height: 30px;
}
.preface p.content{
text-indent: 2em;
}
section > table{
table-layout: fixed;
width: 100%;
margin: 20px 0px;
text-align:center;
word-wrap:break-word;
}
section table td{
padding:5px 0px;
}
</style>
</head>
<body>
<!-- 標題 start -->
<section class="title">
<h2>某報告</h2>
</section>
<!-- 標題 end -->
<!-- 前言 start -->
<section class="preface">
<p>尊敬的用戶:</p>
<p class="content">內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容</p>
</section>
<!-- 前言 end -->
<!-- 彙總統計信息 start -->
<section class="count-info">
<h4>彙總統計信息</h4>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<td>本月筆數</td>
<td>近三個月數量對比</td>
</tr>
<tr>
<td>${curr}</td>
<td>
<table width="80%" border="1" cellspacing="0" cellpadding="0" style="margin: 5px auto;">
<tr>
<td>${one}</td>
<td>${two}</td>
<td>${three}</td>
</tr>
</table>
</td>
</tr>
</table>
</section>
<!-- 彙總統計信息 end -->
<!-- 明細 start -->
<section class="detail">
<h4>明細</h4>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<td width="5%">序號</td>
<td width="15%">列1</td>
<td width="12%">列2</td>
<td width="12%">列3</td>
<td width="12%">列4</td>
<td>列5</td>
</tr>
<#list detailList as ad>
<tr>
<td>${ad_index+1}</td>
<td>${ad.column1}</td>
<td>${ad.column2}</td>
<td>${ad.column3}</td>
<td>${ad.column4}</td>
<td>${ad.column5}</td>
</tr>
</#list>
</table>
</section>
<!-- 明細 end -->
</body>
</html>
三,工具類實現
package com.htf.utils;
/**
* @Auther: admin
* @Date: 2019/8/10 12:59
* @Description:
*/
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;
import freemarker.template.Template;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import javax.servlet.http.HttpServletResponse;
public class PdfTemplateUtil {
//構造器私有,防止別人通過new對象調用
private PdfTemplateUtil() {
}
/**
* @param data 模板數據
* @param templateFileName freemarker模板文件名
* @return : java.io.ByteArrayOutputStream
* @auther : $Mr. Liu$
* @date : 2019/8/9 14:45
* @description : 通過模板導出pdf文件(有返回值)
**/
public static ByteArrayOutputStream createPDF(Map<String, Object> data, String templateFileName) throws Exception {
// 創建一個FreeMarker實例, 負責管理FreeMarker模板的Configuration實例
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
configuration.setClassForTemplateLoading(PdfTemplateUtil.class, "/templates");
ITextRenderer renderer = new ITextRenderer();
OutputStream out = new ByteArrayOutputStream();
StringWriter writer = new StringWriter();
try {
// 設置 css中 的字體樣式(暫時僅支持宋體和黑體) 必須,不然中文不顯示
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 設置模板的編碼格式
configuration.setEncoding(Locale.CHINA, "UTF-8");
// 獲取模板文件
Template template = configuration.getTemplate(templateFileName, "UTF-8");
// 將數據輸出到html中
template.process(data, writer);
writer.flush();
String html = writer.toString();
// 把html代碼傳入渲染器中
renderer.setDocumentFromString(html);
// 設置模板中的圖片路徑 (這裏的images在resources目錄下) 模板中img標籤src路徑需要相對路徑加圖片名 如<img src="images/xh.jpg"/>
String url = PdfTemplateUtil.class.getClassLoader().getResource("static/images").toURI().toString();
renderer.getSharedContext().setBaseURL(url);
renderer.layout();
renderer.createPDF(out, false);
renderer.finishPDF();
out.flush();
return (ByteArrayOutputStream) out;
} finally {
if (writer != null) {
writer.close();
}
if (out != null) {
out.close();
}
}
}
/**
* @param data 模板數據
* @param templateFileName freemarker模板文件名
* @auther : $Mr. Liu$
* @date : 2019/8/9 14:45
* @description : 通過模板導出pdf文件(改進後無返回值)
**/
public static void createPDF(Map<String, Object> data, String templateFileName, String fileName, HttpServletResponse response) throws Exception {
// 創建一個FreeMarker實例, 負責管理FreeMarker模板的Configuration實例
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
configuration.setClassForTemplateLoading(PdfTemplateUtil.class, "/templates");
ITextRenderer renderer = new ITextRenderer();
ByteArrayOutputStream out = new ByteArrayOutputStream();
StringWriter writer = new StringWriter();
try {
// 設置 css中 的字體樣式(暫時僅支持宋體和黑體) 必須,不然中文不顯示
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 設置模板的編碼格式
configuration.setEncoding(Locale.CHINA, "UTF-8");
// 獲取模板文件
Template template = configuration.getTemplate(templateFileName, "UTF-8");
// 將數據輸出到html中
template.process(data, writer);
writer.flush();
String html = writer.toString();
// 把html代碼傳入渲染器中
renderer.setDocumentFromString(html);
// 設置模板中的圖片路徑 (這裏的images在resources目錄下) 模板中img標籤src路徑需要相對路徑加圖片名 如<img src="images/xh.jpg"/>
String url = PdfTemplateUtil.class.getClassLoader().getResource("static/images").toURI().toString();
renderer.getSharedContext().setBaseURL(url);
renderer.layout();
renderer.createPDF(out, false);
out.flush();
renderer.finishPDF();
response.setContentType("application/x-msdownload");
// 告訴瀏覽器,當前響應數據要求用戶干預保存到文件中,以及文件名是什麼 如果文件名有中文,必須URL編碼
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
out.writeTo(response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
if (out != null) {
out.close();
}
}
}
}
四,PdfDataTest實體類
package com.htf.vo;
public class PdfDataTest {
private Integer column1;
private String column2;
private String column3;
private String column4;
private String column5;
public PdfDataTest(Integer column1, String column2, String column3, String column4, String column5) {
this.column1 = column1;
this.column2 = column2;
this.column3 = column3;
this.column4 = column4;
this.column5 = column5;
}
public PdfDataTest() {
}
public Integer getColumn1() {
return column1;
}
public void setColumn1(Integer column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
public String getColumn3() {
return column3;
}
public void setColumn3(String column3) {
this.column3 = column3;
}
public String getColumn4() {
return column4;
}
public void setColumn4(String column4) {
this.column4 = column4;
}
public String getColumn5() {
return column5;
}
public void setColumn5(String column5) {
this.column5 = column5;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("PdfDataTest{");
sb.append("column1=").append(column1);
sb.append(", column2='").append(column2).append('\'');
sb.append(", column3='").append(column3).append('\'');
sb.append(", column4='").append(column4).append('\'');
sb.append(", column5='").append(column5).append('\'');
sb.append('}');
return sb.toString();
}
}
五,Controller實現
package com.htf.controller;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.htf.utils.PdfTemplateUtil;
import com.htf.vo.PdfDataTest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/pdf")
public class PdfController {
/**
* @param : [response]
* @return : void
* @date : 2019/8/10 13:20
* @exception:
* @Description: 有返回值的
*/
@RequestMapping("/export")
public void exportPdf(HttpServletResponse response) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = null;
OutputStream out = null;
try {
Map<String, Object> data = getStringObjectMap();
byteArrayOutputStream = PdfTemplateUtil.createPDF(data, "test.ftl");
// 設置響應消息頭,告訴瀏覽器當前響應是一個下載文件
response.setContentType("application/x-msdownload");
// 告訴瀏覽器,當前響應數據要求用戶干預保存到文件中,以及文件名是什麼 如果文件名有中文,必須URL編碼
String fileName = URLEncoder.encode("月度報告.pdf", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
out = response.getOutputStream();
byteArrayOutputStream.writeTo(out);
byteArrayOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
throw new Exception("導出失敗:" + e.getMessage());
} finally {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
if (out != null) {
out.close();
}
}
}
private Map<String, Object> getStringObjectMap() {
// 模板中的數據,實際運用從數據庫中查詢
Map<String, Object> data = new HashMap<>();
data.put("curr", 1);
data.put("one", 2);
data.put("two", 1);
data.put("three", 6);
List<PdfDataTest> detailList = new ArrayList<>();
detailList.add(new PdfDataTest(123456, "測試", "測試", "測試", "測試"));
detailList.add(new PdfDataTest(111111, "測試", "測試", "測試", "測試"));
detailList.add(new PdfDataTest(222222, "測試", "測試", "測試", "測試"));
data.put("detailList", detailList);
return data;
}
/**
* @param : [response]
* @return : void
* @date : 2019/8/10 13:20
* @exception: Exception
* @Description: 改進後無返回值的
*/
@RequestMapping("/export2")
public void exportPdf2(HttpServletResponse response) throws Exception {
Map<String, Object> data = getStringObjectMap();
String fileName = URLEncoder.encode("xxx報告.pdf", "UTF-8");
try {
PdfTemplateUtil.createPDF(data, "test.ftl", fileName, response);
} catch (Exception e) {
e.printStackTrace();
throw new Exception("生成pdf失敗:" + e.getMessage());
}
}
}
六,用多線程改進
package com.htf.utils;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* @Auther: admin
* @Date: 2019/8/10 16:17
* @Description: 有返回值的多線程
*/
public class PdfTemplateUtil2 implements Callable {
private Map<String, Object> data;
private String templateFileName;
public PdfTemplateUtil2(Map<String, Object> data, String templateFileName) {
this.data = data;
this.templateFileName = templateFileName;
}
@Override
public Object call() throws Exception {
return getObject();
}
private Object getObject() throws IOException {
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
configuration.setClassForTemplateLoading(PdfTemplateUtil2.class, "/templates");
ITextRenderer renderer = new ITextRenderer();
StringWriter writer = new StringWriter();
OutputStream out = new ByteArrayOutputStream();
ITextFontResolver fontResolver = renderer.getFontResolver();
try {
// 設置 css中 的字體樣式(暫時僅支持宋體和黑體) 必須,不然中文不顯示
fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 設置模板的編碼格式
configuration.setEncoding(Locale.CHINA, "UTF-8");
// 獲取模板文件
Template template = configuration.getTemplate(templateFileName, "UTF-8");
// 將數據輸出到html中
template.process(data, writer);
writer.flush();
String html = writer.toString();
// 把html代碼傳入渲染器中
renderer.setDocumentFromString(html);
// 設置模板中的圖片路徑 (這裏的images在resources目錄下) 模板中img標籤src路徑需要相對路徑加圖片名 如<img src="images/xh.jpg"/>
String url = PdfTemplateUtil2.class.getClassLoader().getResource("static/images").toURI().toString();
renderer.getSharedContext().setBaseURL(url);
renderer.layout();
renderer.createPDF(out, false);
renderer.finishPDF();
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (writer != null) {
writer.close();
}
}
return (ByteArrayOutputStream) out;
}
}
package com.htf.utils;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;
public class PdfTemplateUtil3 implements Runnable {
private Map<String, Object> data;
private String templateFileName;
private String fileName;
private HttpServletResponse response;
@Autowired
private PdfTemplateUtil3 pdfTemplate;
public PdfTemplateUtil3(Map<String, Object> data, String templateFileName, String fileName, HttpServletResponse response) {
this.data = data;
this.templateFileName = templateFileName;
this.fileName = fileName;
this.response = response;
}
@Override
public void run() {
try {
ByteArrayOutputStream baos = pdfTemplate.createPDF(data, templateFileName);
response.setContentType("application/x-msdownload");
// 告訴瀏覽器,當前響應數據要求用戶干預保存到文件中,以及文件名是什麼 如果文件名有中文,必須URL編碼
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
try {
baos.writeTo(response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public ByteArrayOutputStream createPDF(Map<String, Object> data, String templateFileName) throws Exception {
// 創建一個FreeMarker實例, 負責管理FreeMarker模板的Configuration實例
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
configuration.setClassForTemplateLoading(PdfTemplateUtil3.class, "/templates");
ITextRenderer renderer = new ITextRenderer();
OutputStream out = new ByteArrayOutputStream();
StringWriter writer = new StringWriter();
try {
// 設置 css中 的字體樣式(暫時僅支持宋體和黑體) 必須,不然中文不顯示
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont("C:/Windows/Fonts/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 設置模板的編碼格式
configuration.setEncoding(Locale.CHINA, "UTF-8");
// 獲取模板文件
Template template = configuration.getTemplate(templateFileName, "UTF-8");
// 將數據輸出到html中
template.process(data, writer);
writer.flush();
String html = writer.toString();
// 把html代碼傳入渲染器中
renderer.setDocumentFromString(html);
// 設置模板中的圖片路徑 (這裏的images在resources目錄下) 模板中img標籤src路徑需要相對路徑加圖片名 如<img src="images/xh.jpg"/>
String url = PdfTemplateUtil3.class.getClassLoader().getResource("static/images").toURI().toString();
renderer.getSharedContext().setBaseURL(url);
renderer.layout();
renderer.createPDF(out, false);
renderer.finishPDF();
out.flush();
return (ByteArrayOutputStream) out;
} finally {
if (writer != null) {
writer.close();
}
if (out != null) {
out.close();
}
}
}
}
package com.htf.controller;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.htf.utils.PdfTemplateUtil2;
import com.htf.utils.PdfTemplateUtil3;
import com.htf.vo.PdfDataTest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
@RestController
@RequestMapping("/pdf2")
public class MultithreadingPdfController {
/**
* @description : 有返回值的線程
**/
@RequestMapping("/export")
public void exportPdf(HttpServletResponse response) throws Exception {
OutputStream out = null;
ByteArrayOutputStream baos = null;
// 模板中的數據,實際運用從數據庫中查詢
Map<String, Object> data = getStringObjectMap();
int pressure = 10;
//併發開啓100個線程調用/rearrange_sale/rearrange接口,查看更新update操作鎖表可能引起的異常。
ExecutorService executorService = new ThreadPoolExecutor(10, 50, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("index-thread-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
List<Future<ByteArrayOutputStream>> futureList = new ArrayList<>(pressure);
Future<ByteArrayOutputStream> result = null;
try {
//利用線程池,開啓多線程模式
for (int idx = 0; idx < pressure; idx++) {
result = (Future<ByteArrayOutputStream>) executorService.submit(new PdfTemplateUtil2(data, "test.ftl"));
futureList.add(result);
}
//調用future方法阻塞當前線程,直至所有的分線程執行完畢
for (Future<ByteArrayOutputStream> future : futureList) {
System.out.println("future.get(): " + future.get());
}
baos = result.get();
// 設置響應消息頭,告訴瀏覽器當前響應是一個下載文件
response.setContentType("application/x-msdownload");
// 告訴瀏覽器,當前響應數據要求用戶干預保存到文件中,以及文件名是什麼 如果文件名有中文,必須URL編碼
String fileName = URLEncoder.encode("xxx報告.pdf", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
out = response.getOutputStream();
baos.writeTo(out);
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (baos != null) {
baos.close();
}
if (out != null) {
out.close();
}
}
}
/**
* @description : 無返回值的線程
**/
@RequestMapping("/export1")
public void exportPdf1(HttpServletResponse response) throws Exception {
// 模板中的數據,實際運用從數據庫中查詢
Map<String, Object> data = getStringObjectMap();
String fileName = URLEncoder.encode("xxx報告.pdf", "UTF-8");
int pressure = 10;
ExecutorService executorService = new ThreadPoolExecutor(10, 50, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("index-thread-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
try {
//利用線程池,開啓多線程模式
for (int idx = 0; idx < pressure; idx++) {
executorService.execute(new PdfTemplateUtil3(data, "test.ftl", fileName, response));
}
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
private Map<String, Object> getStringObjectMap() {
// 模板中的數據,實際運用從數據庫中查詢
Map<String, Object> data = new HashMap<>();
data.put("curr", 1);
data.put("one", 2);
data.put("two", 1);
data.put("three", 6);
List<PdfDataTest> detailList = new ArrayList<>();
detailList.add(new PdfDataTest(123456, "測試", "測試", "測試", "測試"));
detailList.add(new PdfDataTest(111111, "測試", "測試", "測試", "測試"));
detailList.add(new PdfDataTest(222222, "測試", "測試", "測試", "測試"));
data.put("detailList", detailList);
return data;
}
}