前言
在Spring中视图分为逻辑视图和非逻辑视图。逻辑视图是需要视图解析器(ViewResolver)去定位解析的,如:JstlView等。而非逻辑视图则不需要定位视图的物理位置,它只需要直接将数据模型进行渲染展示给用户即可,如:MappingJackson2JsonView(它会将数据模型渲染为Json数据集返回给用户),以及本文中使用的AbstractPdfView。
注:本文是基于上一篇文章中的项目(实战:在SpringBoot中使用MongoDB)基础上进行开发的(偷个懒~),主要实现功能是从MongoDB中读取数据,并将数据写入PDF中,最后返回给用户。
一、在pom.xml中加入PDF依赖
<!-- PDF依赖包 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>core-renderer</artifactId>
<version>R8</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.12</version>
</dependency>
这样项目就导入了关于PDF的包。在Spring中只需要继承AbstractPdfView抽象类,并实现抽象方法即可完成PDF视图的配置。但是我们一般不仅仅只需要一种格式的PDF视图,而为了适应不同业务需求,我们一般先定义导出的接口。
二、定义PDF导出接口
package com.scb.mongodemo.service;
import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public interface PdfExportService {
void make(Map<String, Object> map, Document document,
PdfWriter pdfWriter, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse);
}
可以看到,PdfExportService就是个普通的接口类,里面有个make方法,此方法的参数与AbstractPdfView的抽象类相同。接下来,我们就可以继承AbstractPdfView抽象来配置PDF视图了。
三、继承AbstractPdfView
package com.scb.mongodemo.service;
import com.lowagie.text.*;
import com.lowagie.text.pdf.PdfWriter;
import org.springframework.web.servlet.view.document.AbstractPdfView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public class PdfView extends AbstractPdfView {
private PdfExportService pdfExportService=null;
public PdfView(PdfExportService pdfExportService){
this.pdfExportService=pdfExportService;
}
@Override
protected void buildPdfDocument(Map<String, Object> map, Document document,
PdfWriter pdfWriter, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
pdfExportService.make(map,document,pdfWriter,httpServletRequest,httpServletResponse);
}
}
从上诉代码可以看到,我们是通过调用PDF导出接口类PdfExportService的make方法来执行buildPdfDocument方法的。这样做的好处就是,可以通过创建PdfExportService接口的子类,并通过依赖注入的方式实现不同的PDF细节。
四、控制层
package com.scb.mongodemo.controller;
import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.scb.mongodemo.entity.Role;
import com.scb.mongodemo.entity.User;
import com.scb.mongodemo.repository.UserRepository;
import com.scb.mongodemo.service.PdfExportService;
import com.scb.mongodemo.service.PdfView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import java.awt.*;
import java.util.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserRepository userRepository;
/**
* 这是上章的内容,与本章无关可以注释掉
*
@GetMapping("/{userName}")
public List<User> findByUserName(@PathVariable String userName) {
return userRepository.findByUserNameLike(userName);
}
@PostMapping("/initUsers")
public List<User> initUsers(){
List<User> saveUsers=new ArrayList<>();
for (int i=1;i<=10;i++){
User user=new User();
user.setId((long)i);
user.setUserName("user_name_"+i);
user.setNote("user_note_"+i);
Role role=new Role();
role.setId((long)i);
role.setRoleName("role_name_"+i);
role.setNote("role_note_"+i);
List<Role> roles=new ArrayList<>();
roles.add(role);
user.setRoles(roles);
saveUsers.add(userRepository.save(user));
}
return saveUsers;
}
*/
@GetMapping("/export/pdf")
public ModelAndView exportPdf(String userName){
List<User> userList=userRepository.findByUserNameLike(userName);
View view=new PdfView(exportService());
ModelAndView mv=new ModelAndView();
mv.setView(view);
mv.addObject("userList",userList);
return mv;
}
@SuppressWarnings("unchecked")
private PdfExportService exportService(){
//使用Lambda表达式自定义导出
return (model,document,writer,request,response)->{
try{
document.setPageSize(PageSize.A4);
document.addTitle("User Info");
//换行
document.add(new Chunk("\n"));
//创建表格,3列
PdfPTable table=new PdfPTable(3);
Font f8=new Font();
f8.setColor(Color.BLUE);
f8.setStyle(Font.BOLD);
//单元格
PdfPCell cell=new PdfPCell(new Paragraph("id",f8));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell=new PdfPCell(new Paragraph("user_name",f8));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell=new PdfPCell(new Paragraph("note",f8));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
List<User> userList=(List<User>)model.get("userList");
Font font=new Font();
font.setColor(Color.BLACK);
//获取数据模型中的userList,并填入表格
for (User user:userList){
document.add(new Chunk("\n"));
cell=new PdfPCell(new Paragraph(user.getId()+"",font));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell=new PdfPCell(new Paragraph(user.getUserName(),font));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell=new PdfPCell(new Paragraph(user.getNote(),font));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
}
//在文档中加入表格
document.add(table);
}catch (DocumentException e){
e.printStackTrace();
}
};
}
}
exportPdf方法:先查询后台数据得到用户列表,在放入模型和视图(ModelAndView)中,接着设置了一个视图(PdfView)。PdfView通过调用下面的exportService方法来初始化的。对于exportService方法中的具体内容且听我下文在说。
五、运行结果
我们浏览器访问http://localhost:8080/user/export/pdf?userName=user_name,可以得到以下结果:
可以看到,自定义的PDF视图已经成功渲染出来了。
六、exportService方法详解
在exportService方法中,我们是通过使用iText的相关方法来生成PDF的。
iText是著名开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。详细教程如下:
简单入门:iText入门学习 深入学习:itext7学习汇总
七、后记
上诉代码中,我们不仅需要写PDF的模板还要写入数据。而生成PDF模板是相当繁琐的,那么我们能不能通过导入一个PDF文件,然后只要往该文件中写入数据就可以了呢?当然可以,详见以下教程: