近期做一个生成PDF的直接下载功能,当然一般有两种方法,一种是代码实现其内容,代码实现样式可参考 http://rensanning.iteye.com/blog/1538689,另一种通过制作模板向其添加数据生成PDF下载,模板制作方式参考: http://wenku.baidu.com/linkurl=3dKL69zhDcMywL4hNmvmLHP2MvG2oT6ahfqijlIVpcvz7W_xi6fYEoWQQBMzi8b2360KOvJFB9CCgKbvAN01Zr2k0LKnyOqPaxcm5vYdsCO;
由于本业务要求输出的PDF较为复杂,所以选择通过模板实现其样式进行PDF的输出,首先准备相应的jar包,
核心包:
com.itextpdf
itextpdf
5.4.3
中文支持包:
com.itextpdf
itext-asian
5.2.0
中文支持的话其他版本有问题,上述两个版本同时使用通过测试的;
因为项目用的是SpringMVC,所以控制层代码:
@RequestMapping("/download")
public String pdfDownload(Model model) {
//添加数据
model.addAttribute("data", "大哥"));
return "contract";
}
配置文件:
Spring框架给我们集成了读取模板生成PDF的类AbstractPdfStamperView;但是其只集成的是itext2.7.0以前的版本,现在已变成itextpdf5.4以上,itext有些方法被itextpdf重写,导致我们不能直接使用AbstractPdfStamperView,解决方式是我们写个类去继承AbstractUrlBasedView,复制AbstractPdfStamperView的方法,重新引入相关方法,
/**
* PDF模板视图 ;重新定义AbstractPdfStamperView类重写其中相关方法
*/
public abstract class AbstractPdf4StamperView extends AbstractUrlBasedView {
public AbstractPdf4StamperView(){
setContentType("application/pdf");
}
@Override
protected boolean generatesDownloadContent() {
return true;
}
/**
* 通过model判断读取模板 ,关闭相关流
*/
@Override
protected final void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) {
// IE workaround: write into byte array first.
ByteArrayOutputStream baos = createTemporaryOutputStream();
String path = request.getSession().getServletContext().getRealPath("/");
String TemplatePDF = path + "WEB-INF/pdftemple/"+temp+".pdf";
PdfReader reader = null;
PdfStamper stamper = null;
try {
reader = new PdfReader(TemplatePDF);
stamper = new PdfStamper(reader, baos);
mergePdfDocument(model, stamper, request, response);
} catch (IOException ex) {
LoggerUtil.error("读取模板异常", ex);
} catch (PDFException e) {
LoggerUtil.error("生成pdf异常", e);
} catch (DocumentException dx){
LoggerUtil.error("生成pdf异常", dx);
} finally{
if (stamper != null) {
try {
stamper.close();
} catch (IOException e) {
LoggerUtil.error("关闭IO异常", e);
} catch (DocumentException dx){
LoggerUtil.error("生成失败", dx);
}
}
if(reader != null ){
reader.close();
}
}
// Flush to HTTP response.
response.setContentType(getContentType());
response.setContentLength(baos.size());
// Flush byte array to servlet output stream.
ServletOutputStream out = null;
try {
out = response.getOutputStream();
baos.writeTo(out);
out.flush();
} catch (IOException e) {
LoggerUtil.error("writeToResponse is Exception", e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LoggerUtil.error("关闭IO异常", e);
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
LoggerUtil.error("关闭IO异常", e);
}
}
}
}
protected PdfReader readPdfResource() throws IOException {
return new PdfReader(getApplicationContext().getResource(getUrl()).getInputStream());
}
protected abstract void mergePdfDocument(Map model, PdfStamper stamper,
HttpServletRequest request, HttpServletResponse response) throws PDFException;
}
原本renderMergedOutputModel方法只有下面几行:
@Override
protected final void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// IE workaround: write into byte array first.
ByteArrayOutputStream baos = createTemporaryOutputStream();
PdfReader reader = readPdfResource();
PdfStamper stamper = new PdfStamper(reader, baos);
mergePdfDocument(model, stamper, request, response);
stamper.close();
// Flush to HTTP response.
writeToResponse(response, baos);
}
在测试过程中发现已加载的模板无法删除,才发现框架提供的方法没有关闭相关的流,所以也在该类下手动关闭了相关流,注意的是模板放在maven构建的resource下无法被加载,也能是模板被编译的故,具体原因待解,所以把模板放在了WEB-INF下,这也是原先框架放置的地方,因为在配置pdf模板视图是有个URL参数就是添的web-inf;只不过我要选择不同的模板所以重写了读取模板的方法。
接下来是写一个类去继承AbstractPdf4StamperView然后重写mergePdfDocument方法进行数据的添加:
package com.netease.cpp.view;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Calendar;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfStamper;
import com.netease.cpp.exception.PDFException;
import com.netease.cpp.util.LoggerUtil;
/**
* 生成PDF
*/
public class PdfStamperView extends AbstractPdf4StamperView {
@SuppressWarnings("unchecked")
@Override
protected void mergePdfDocument(Map model, PdfStamper stamper, HttpServletRequest request,HttpServletResponse response) throws PDFException {
// 设置response方式,使执行此controller时候自动出现下载页面,而非直接使用pdf打开
response.setContentType("APPLICATION/OCTET-STREAM");
try {
response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(filename, "UTF-8"));
} catch (UnsupportedEncodingException e) {
LoggerUtil.error("文件名编码失败", e);
throw new PDFException(e);
}
AcroFields fields = stamper.getAcroFields();
try {
fillData(fields,model);
} catch (IOException ex) {
LoggerUtil.error("向pdf添加数据失败", ex);
throw new PDFException(ex);
} catch (DocumentException dx){
LoggerUtil.error("向pdf添加数据失败", dx);
}
stamper.setFormFlattening(true);//必须有,否则输出的pdf是一个表单
}
//给模板添加数据
@SuppressWarnings("unchecked")
private void fillData(AcroFields fields,Map model)throws IOException, DocumentException {
Map data=( Map) model.get("data");
for (String key : data.keySet()) {
String value = data.get(key);
fields.setField(key, value);
}
}
}