SpringMVC支持的视图有不少种,JSP的视图为JstlView,同时也支持其余模版:FreeMaker对应的视图为FreeMarkerView,Velocity对应的视图为VelocityView。另外还支持Excel及PDF的视图。java
在DispatcherServlet的核心处理doDispatch方法最后,视图的渲染由render方法执行。获取View对象,而后调用View对象的render方法完成数据绑定和视图渲染。git
咱们从View接口开始,深刻介绍下SpringMVC视图的实现过程。spring
View接口只定义了两个方法: getContentType和renderapache
public interface View { String getContentType(); void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
getContentType由具体View来实现,而render方法则由AbstractView定义了模板实现。mvc
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 合并全部的数据Model Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); // 响应前准备 prepareResponse(request, response); // 渲染视图并输出数据 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
createMergedOutputModel方法将全部须要暴露出去的数据合并成一个Map,prepareResponse方法执行响应前准备。renderMergedOutputModel是抽象方法,由不一样的子类本身实现视图的渲染和数据的输出。app
SpringMVC默认配置的视图解析器ViewResolver为InternalResourceViewResolver,实现了ViewResolver接口的resolveViewName方法,最终调用的是buildView方法,返回InternalResourceView。ide
protected AbstractUrlBasedView buildView(String viewName) throws Exception { InternalResourceView view = (InternalResourceView) super.buildView(viewName); if (this.alwaysInclude != null) { view.setAlwaysInclude(this.alwaysInclude); } view.setPreventDispatchLoop(true); return view; }
InternalResourceView做为JSP默认的视图,支持了正常的JSP请求响应。来看下其实现的renderMergedOutputModel方法。函数
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 将Model数据设置到request属性上 exposeModelAsRequestAttributes(model, request); // 暴露帮助类做为request属性 exposeHelpers(request); // 决定要响应的路径 String dispatcherPath = prepareForRendering(request, response); // 得到目标资源的RequestDispatcher RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); // include操做 if (useInclude(request, response)) { response.setContentType(getContentType()); rd.include(request, response); } else { // 默认为forward操做 rd.forward(request, response); } }
将Model数据绑定到request属性上,就是遍历Model数据,而后调用request的setAttribute方法。注意,若是Model数据中的属性设置为null,则移除request对应的属性。工具
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { for (Map.Entry<String, Object> entry : model.entrySet()) { String modelName = entry.getKey(); Object modelValue = entry.getValue(); if (modelValue != null) { request.setAttribute(modelName, modelValue); } else { request.removeAttribute(modelName); } } }
视图的渲染和数据的输出则是由Servlet的RequestDispatcher来执行。oop
JstlView是InternalResourceView的子类,实现了exposeHelpers,将JSTL相关属性绑定到request上。
InternalResourceView的用法很简单,只要经过ModelAndView设置ViewName便可。
@GetMapping("/example") public ModelAndView index(){ return new ModelAndView("example/example"); }
经常使用的response方式都是forward,但有时也会用到重定向操做。在SpringMVC中,使用重定向的方式由两种:
使用RedirectView
直接返回ViewName,加上redirect:前缀
// RedirectView @GetMapping("/redirectView") public ModelAndView redirect(RedirectAttributes attrs){ ModelAndView mav = new ModelAndView(); mav.setView(new RedirectView("example")); return mav; } // redirect: @GetMapping("/redirect/prefix") public String redirectPrefix(){ return "redirect:example"; }
须要注意的是重定向的路径会覆盖当前URL最后一个/后的路径,若是想替换前面的路径,须要使用..符号。
RedirectView的renderMergedOutputModel方法的实现,主要有两部分:重定向属性的保存和执行重定向。
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException { // 获取重定向url String targetUrl = createTargetUrl(model, request); targetUrl = updateTargetUrl(targetUrl, model, request, response); // 使用FlashMap保存重定向的Model数据 FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); if (flashMapManager == null) { throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); } flashMapManager.saveOutputFlashMap(flashMap, request, response); } // 执行重定向 sendRedirect(request, response, targetUrl, this.http10Compatible); }
使用FlashMap的机制来保存重定向携带的Model数据,结合DispatcherServlet的doService方法中的FlashMap的获取和更新,完成重定向操做时Model数据的转移。
执行重定向时直接使用response的sendRedirect方法执行。
SpringMVC整合了POI对Excel的操做,定义了View实现类便捷地处理Excel。SpringMVC在4.2版本后,结合poi-ooxml使用流式函数支持office 2007的XLSX,不过须要poi的版本在3.9及以上。 能够直接引入poi-ooxml:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.14</version> </dependency>
先使用@ModelAttribute定义一个数据Model,返回一个表格数据
@ModelAttribute private void getUser(Model model){ List<String> headers = new ArrayList<>(); headers.add("Name"); headers.add("Sex"); headers.add("Age"); List<User> users = new ArrayList<>(); users.add(new User("Jack", "M", 20)); users.add(new User("Emily", "W", 24)); users.add(new User("Tom", "M", 23)); Map<String, Object> data = new HashMap<>(); data.put("header", headers); data.put("user", users); model.addAttribute("model", data); }
表格输出的结果以下:
Name | Sex | Age |
---|---|---|
Jack | M | 20 |
Emily | W | 24 |
Tom | M | 23 |
实例化AbstractXlsxStreamingView内部类,完成Excel的操做,经过ModelAndView返回视图和数据。
@GetMapping("/excel") public ModelAndView renderExcel(@ModelAttribute("model") Map<String, Object> model){ AbstractXlsxStreamingView excelView = new AbstractXlsxStreamingView() { @SuppressWarnings("unchecked") @Override protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { Sheet sheet = workbook.createSheet(); // header Row headerRow = sheet.createRow(0); List<String> headerNames = (List<String>)model.get("header"); for(int column=0; column<headerNames.size(); column++){ headerRow.createCell(column).setCellValue(headerNames.get(column)); } // body List<User> users = (List<User>)model.get("user"); for(int row=1; row<users.size();row++){ Row userRow = sheet.createRow(row); userRow.createCell(0).setCellValue(users.get(row).getName()); userRow.createCell(1).setCellValue(users.get(row).getSex()); userRow.createCell(2).setCellValue(users.get(row).getAge()); } // set output file name response.setHeader( "Content-disposition", "attachment; filename=" + URLEncoder.encode("user", "utf-8") + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date()) + ".xlsx"); } }; return new ModelAndView(excelView, model); }
能够看到Excel的业务处理由用户自定义的buildExcelDocument方法完成。其实在AbstractXlsxStreamingView的父类AbstractXlsView中,将AbstractView的renderMergedOutputModel方法有进行了模板化。
protected final void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 建立Excel的Workbook Workbook workbook = createWorkbook(model, request); // 将Excel的处理委托给子类 buildExcelDocument(model, workbook, request, response); // 设置contentType为application/vnd.ms-excel response.setContentType(getContentType()); // 将数据写到response中 renderWorkbook(workbook, response); }
一样的方式,对PDF的输出,SpringMVC也结合了iText,提供了一个方便的View实现类AbstractPdfView。复用ExcelView中的Model数据,实现上面表格的PDF输出。
@GetMapping("/pdf") public ModelAndView renderPdf(@ModelAttribute("model") Map<String, Object> model){ AbstractPdfView pdfView = new AbstractPdfView() { @Override protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { List<String> headerNames = (List<String>)model.get("header"); List<User> users = (List<User>)model.get("user"); document.newPage(); PdfPTable table = new PdfPTable(3); table.setHeaderRows(0); // header for(int i=0;i<headerNames.size();i++){ PdfPCell cell = new PdfPCell(); cell.setBackgroundColor(Color.GRAY); cell.setPhrase(new Phrase(headerNames.get(i))); table.addCell(cell); } // body for(int row=0; row<users.size();row++){ PdfPCell nameCell = new PdfPCell(); nameCell.setPhrase(new Phrase(users.get(row).getName())); table.addCell(nameCell); PdfPCell sexCell = new PdfPCell(); sexCell.setPhrase(new Phrase(users.get(row).getSex())); table.addCell(sexCell); PdfPCell ageCell = new PdfPCell(); ageCell.setPhrase(new Phrase(users.get(row).getAge().toString())); table.addCell(ageCell); } document.add(table); } }; return new ModelAndView(pdfView, model); }
在AbstractPdfView中,也是将renderMergedOutputModel方法模板化。
protected final void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // IE workaround: write into byte array first. ByteArrayOutputStream baos = createTemporaryOutputStream(); // 建立文档 Document document = newDocument(); PdfWriter writer = newWriter(document, baos); prepareWriter(model, writer, request); // 创建Pdf元数据,由子类实现 buildPdfMetadata(model, document, request); // 处理Pdf内容 document.open(); buildPdfDocument(model, document, writer, request, response); document.close(); // 将数据刷新到response writeToResponse(response, baos); }
以上咱们介绍了SpringMVC的View的多种实现,都是基于统一的模板,而后整合一些第三方工具提供了便捷的处理方式,也能够继承AbstractView自定义处理过程,来支持特殊的需求。但愿你们有所收获!