一,依赖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;
}
}