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自定義處理過程,來支持特殊的需求。但願你們有所收穫!