本文基于springboot环境来进行测试,没有这个环境的,可以参考这里,隔空这位同学的用心总结。下面我们正式开始。
先看下ModelAndView源码:
public class ModelAndView {
// 逻辑视图地址
@Nullable
private Object view;
// 数据
@Nullable
private ModelMap model;
}
其中的Object view
代表的就是视图,在jsp环境中可以认为就是jsp页面,ModelMap model
就是数据,来看下ModelMap的定义:
public class ModelMap extends LinkedHashMap<String, Object> {}
可以看到继承了java.util.LinkedHashMap
,所以其也是一个java.util.Map。通过Object view
就有了页面,通过字典数据结构ModelMap model
就有了页面需要的数据,二者结合就可以生成最终展示给用户的页面了。对于数据spring还提供了另外的选择,即接口org.springframework.ui.Model
该接口在spring-context
模块中,可以认为该接口是ModelMap
外的另外一个选择。源码如下:
public interface Model {
// 添加视图需要的数据
Model addAttribute(String attributeName, @Nullable Object attributeValue);
}
其实现类
public class ExtendedModelMap extends ModelMap implements Model {}
可以看到其不仅仅实现了Model
接口,还继承了类ModelMap
,因此兼具二者的能力
。接下来我们通过一个完整的例子来看一下。
我们先来定义jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
${user.id}---${user.userName}---${user.note}
body>
html>
public class User {
private Long id;
private String userName;
private String note;
...getter setter toString...
}
@RequestMapping("/useModelAndView")
public ModelAndView useModelAndView() {
ModelAndView mv = new ModelAndView();
// 设置数据模型
User user = new User();
user.setId(1L);
user.setUserName("张三");
user.setNote("张三的歌");
mv.addObject("user", user);
// 设置视图为user.jsp
mv.setViewName("user");
return mv;
}
@RequestMapping("/userModel")
public String userModel(Model model) {
User user = new User();
user.setId(2L);
user.setUserName("李四");
user.setNote("李四的小姨子");
model.addAttribute("user", user);
return "user";
}
@RequestMapping("/useModelMap")
public ModelAndView useModelMap(ModelMap modelMap) {
User user = new User();
user.setId(3L);
user.setUserName("王五");
user.setNote("王五的表姐");
ModelAndView mv = new ModelAndView();
// 设置视图
mv.setViewName("user");
// 通过ModelMap设置数据
modelMap.addAttribute("user", user);
return mv;
}
除了我们例子中的jsp视图(逻辑视图,即需要视图解析器),还有很多其它的视图,比如json视图,pdf视图,excel视图,但是这些视图都是非逻辑视图,即不需要视图解析器,所有的视图都会实现View接口,源码如下:
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
@Nullable
default String getContentType() {
return null;
}
// 渲染数据方法
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
这里我们以pdf为例进行说明,pdf视图定义了接口AbstractPdfView
,源码如下:
public abstract class AbstractPdfView extends AbstractView {
protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception;
}
如果想要自定义PDF视图,只需要继承该抽象类,并实现抽象方法buildPdfDocument
就可以了。
@FunctionalInterface
public interface PdfExportService {
public void make(
Map<String, Object> model,
Document document,
PdfWriter pdfWriter,
HttpServletRequest request,
HttpServletResponse response);
}
/**
* 通过集成AbstractPdfView定义pdf视图
*/
public class PdfView extends AbstractPdfView {
private PdfExportService pdfExportService;
public PdfView(PdfExportService pdfExportService) {
this.pdfExportService = pdfExportService;
}
// pdf数据渲染接口
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document,
PdfWriter pdfWriter,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
pdfExportService.make(model, document, pdfWriter, request, response);
}
}
// 导出pdf
@RequestMapping("/export/pdf")
public ModelAndView exportPdf() {
ModelAndView mv = new ModelAndView();
// 需要渲染到pdf文档上的用户列表数据
List<User> userList = new ArrayList<>();
userList.add(new User(1L, "zhangsan", "the song of zhangsan"));
userList.add(new User(2L, "lisi", "the xiaoyizi of lisi"));
userList.add(new User(3L, "wangwu", "the biaojie of wangwu"));
userList.add(new User(4L, "zhaoliu", "the guma of wangwu"));
mv.addObject("userList", userList);
// 定义PDF视图并设置
View pdfView = new PdfView(exportService());
mv.setView(pdfView);
return mv;
}
private PdfExportService exportService() {
return (model, document, pdfWriter, request, response) -> {
try {
// A4纸张
document.setPageSize(PageSize.A4);
// 设置页面标题
document.addTitle("用户信息");
// 换行
document.add(new Chunk("\n"));
// 表格4行
PdfPTable pdfPTable = new PdfPTable(3);
// 单元格
//PdfPCell cell = null;
// 字体,定义为蓝色加粗
Font f8 = new Font();
f8.setColor(Color.BLUE);
f8.setStyle(Font.BOLD);
String[] headTileArr = new String[]
{ "id", "username", "note" };
for (String headTitle : headTileArr) {
// 表格的表头标题
PdfPCell cell = new PdfPCell(new Paragraph(headTitle, f8));
// 居中
cell.setHorizontalAlignment(1);
// 添加到表格中
pdfPTable.addCell(cell);
}
// 设置数据到表格中
List<User> userList = (List<User>) model.get("userList");
for (User user : userList) {
PdfPCell idCell = new PdfPCell(new Paragraph(user.getId() + ""));
pdfPTable.addCell(idCell);
PdfPCell usernameCell = new PdfPCell(new Paragraph(user.getUserName()));
pdfPTable.addCell(usernameCell);
PdfPCell noteCell = new PdfPCell(new Paragraph(user.getNote()));
pdfPTable.addCell(noteCell);
}
// 添加表格到文档中
document.add(pdfPTable);
} catch (Exception e) {
e.printStackTrace();
}
};
}