PDF的生成与下载

近期做一个生成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);
	    }
    }	  

}

代码中写出了一些注意事项,这样就能实现PDF的下载了






你可能感兴趣的:(PDF的生成与下载)