SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html
本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何完成視圖解析的java
在SpringMVC源碼閱讀:攔截器分析過doDispatch的運行過程,這裏再分析一遍git
回到DispatcherServlet類的doDispatch方法,看看doDispatch如何獲取ModelAndViewgithub
HandlerMapping根據request得到HandlerExecutionChainweb
根據HandlerExecutionChain獲取HandlerAdapterspring
HandlerAdapter根據request,response和HandlerExecutionChain調用handle方法返回ModelAndViewapi
而後交由processDispatchResult處理瀏覽器
1023行檢測從doDispatch方法運行到此是否有異常mvc
1037行根據ModelAndView渲染視圖app
1259根據視圖名稱解析成View對象
1282行調用AbstractView的render方法進行渲染
點進去,這裏以解析ftl作測試
301行建立包含動態值和靜態屬性的Map
302爲待渲染的響應作準備
303行調用子類實現方法渲染這個包含動態值和靜態屬性的Map
接下來會進入AbstractTemplateView的renderMergedOutputModel方法,AbstractTemplateView是AbstractView的子類
AbstractTemplateView的renderMergedOutputModel方法會調用內部renderMergedTemplateModel方法,該方法被子類FreemarkerView實現
打開FreemarkerView的renderMergedTemplateModel方法
進入doRender方法,該方法根據response渲染FreeMarker視圖
275行exposeModelAsRequestAttributes方法在父類AbstractView被實現,該方法把Model設置到request中
277行建立Freemarker模板模型
284行處理、加工Freemarker模板給response
再看看FreeMarkerViewResolver作了什麼,打開類接口繼承圖
構造函數設置了前綴和後綴,再看看AbstractTemplateViewResolver
104~108行給視圖設置屬性
咱們發現103行super引用了父類,點開進入UrlBasedViewResolver的buildView方法
532行拼接前綴+視圖名稱+後綴,例如"employee/form.ftl",並設置url
534~553行判斷屬性是否爲空,非空則設置到View中
再看createView方法
460行若是以"redirect:"開頭,交給RedirectView處理
467行若是以"forward:"開頭,交給InternalResourceView處理
Freemarker解析邏輯至此分析完畢
如今看JSP的解析流程
回到AbstractView類的render方法,剛剛咱們分析過
304行此時renderMergedOutputModel方法被子類InternalResourceView實現
139行將Model設置到Request中
142將輔助性參數設置到Request中
145行獲取視圖文件路徑
148行獲取請求分發器
155到170行若是response已經提交,則把資源文件歸入到response中,不然調用forward方法轉發
再看下InternalResourceViewResolver,該類輔助InternalResourceView,爲其設置屬性,其他的ViewResolver的實現類分析同Freemarker
自定義視圖解析器,"jsp:"開頭構造InternalResourceView解析jsp,以"freemarker:"開頭則構造FreemarkerView解析ftl
這樣作的好處是能夠區分同名的jsp和ftl
public class CustomViewResolver extends UrlBasedViewResolver { public static final String JSP_URL_PREFIX = "jsp:"; public static final String FTL_URL_PREFIX = "freemarker:"; private static final boolean jstlPresent = ClassUtils.isPresent( "javax.servlet.jsp.jstl.core.Config", CustomViewResolver.class.getClassLoader()); private Boolean exposePathVariables = false; private boolean exposeRequestAttributes = false; private boolean allowRequestOverride = false; private boolean exposeSessionAttributes = false; private boolean allowSessionOverride = false; private boolean exposeSpringMacroHelpers = true; public CustomViewResolver() { this.setViewClass(FreeMarkerView.class); } @Override protected AbstractUrlBasedView buildView(String viewName) throws Exception { if(viewName.startsWith(FTL_URL_PREFIX)) { return buildFreemarkerView(viewName.substring(FTL_URL_PREFIX.length())); } else if(viewName.startsWith(JSP_URL_PREFIX)) { Class viewCls = jstlPresent ? JstlView.class : InternalResourceView.class; return build(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp"); } else { //默認以freemarker處理 return buildFreemarkerView(viewName); } } /** * @Author 谷天樂 * @Description 使用UrlBasedViewResolver的buildView方法 * 由於CustomViewResolver重寫了buildView,再也不執行UrlBasedViewResolver的buildView方法 * @Date 2019/1/17 11:26 * @Param [viewClass, viewName, prefix, suffix] * @return org.springframework.web.servlet.view.AbstractUrlBasedView **/ private AbstractUrlBasedView build(Class viewClass, String viewName, String prefix, String suffix) { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); view.setUrl(prefix + viewName + suffix); String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); if (this.exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } return view; } /** * @Author 谷天樂 * @Description 使用AbstractTemplateViewResolver的buildView,爲view設置屬性 * Freemarker解析所需 * * @Date 2019/2/7 10:22 * @Param [viewName] * @return org.springframework.web.servlet.view.AbstractUrlBasedView **/ private AbstractUrlBasedView buildFreemarkerView(String viewName) throws Exception { AbstractTemplateView view = (AbstractTemplateView) build(FreeMarkerView.class, viewName, "", getSuffix()); view.setExposeRequestAttributes(this.exposeRequestAttributes); view.setAllowRequestOverride(this.allowRequestOverride); view.setExposeSessionAttributes(this.exposeSessionAttributes); view.setAllowSessionOverride(this.allowSessionOverride); view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers); return view; } public boolean isExposeRequestAttributes() { return exposeRequestAttributes; } public void setExposeRequestAttributes(boolean exposeRequestAttributes) { this.exposeRequestAttributes = exposeRequestAttributes; } public boolean isExposeSessionAttributes() { return exposeSessionAttributes; } public void setExposeSessionAttributes(boolean exposeSessionAttributes) { this.exposeSessionAttributes = exposeSessionAttributes; } public boolean isExposeSpringMacroHelpers() { return exposeSpringMacroHelpers; } public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) { this.exposeSpringMacroHelpers = exposeSpringMacroHelpers; } }
在dispatcher-servlet.xml加入自定義視圖解析器
<bean class="org.format.demo.custom.CustomViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".ftl"/> <property name="contentType" value="text/html;charset=utf-8"/> <property name="exposeRequestAttributes" value="true"/> <property name="exposeSessionAttributes" value="true"/> <property name="exposeSpringMacroHelpers" value="true"/> <property name="requestContextAttribute" value="request"/> </bean>
引入freemarker和jsp的配置,配置在前遇到同名狀況具有更高的優先級,好比index.ftl和index.jsp會優先解析index.ftl
<!--遇到同名的ftl和jsp文件,配置在前的解析器優先級更高--> <import resource="classpath:springConfig/viewConfig/freemarker.xml"/> <import resource="classpath:springConfig/viewConfig/jsp.xml"/>
測試Controller
@Controller @RequestMapping(value = "/tvrc") public class TestViewResolverController { @RequestMapping("jsp") public ModelAndView jsp(ModelAndView view) { view.setViewName("jsp:tvrc/test"); return view; } @RequestMapping("/ftl") public ModelAndView freemarker(ModelAndView view) { view.setViewName("freemarker:tvrc/test"); return view; } }
瀏覽器輸入http://localhost:8080/springmvcdemo/tvrc/jsp
瀏覽器輸入http://localhost:8080/springmvcdemo/tvrc/ftl
測試Controller
@Controller @RequestMapping("otherview") public class OtherViewController { @RequestMapping("/pdf") public ModelAndView mypdf() { ModelAndView mav = new ModelAndView(); //添加自定義視圖 mav.setView(new MyPdfView()); List<String> list = new ArrayList<String>(); for (int i = 0; i < 10; i++) { list.add(i+""); } mav.addObject("list", list); return mav; } @RequestMapping("/excel") public ModelAndView myexcle() { ModelAndView mav = new ModelAndView(); //添加視圖 mav.setView(new MyExcelView()); List list = new ArrayList<String>(); for (int i = 0; i < 10; i++) { list.add(i+""); } mav.addObject("list", list); return mav; } }
建立測試類繼承AbstractPdfView,重寫buildPdfDocument方法
public class MyPdfView extends AbstractPdfView { @Override protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { List list = (List) model.get("list"); for (int i = 0; i < list.size(); i++) { //將數據加載到視圖上 document.add(new Paragraph((String)(list.get(i)))); } } }
瀏覽器輸入http://localhost:8080/springmvcdemo/otherview/pdf
建立測試類繼承AbstractExcelView,重寫buildExcelDocument方法
public class MyExcelView extends AbstractExcelView { @Override protected void buildExcelDocument(Map<String, Object> model, HSSFWorkbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { HSSFSheet sheet;//工做簿的名字 HSSFCell cell;//單元格 sheet = workbook.createSheet("spring"); sheet.setDefaultColumnWidth(12); List list = (List) model.get("list"); for (int i = 0; i < list.size(); i++) { cell = getCell(sheet, 0, i); setText(cell, (String) list.get(i)); } } }
瀏覽器輸入http://localhost:8080/springmvcdemo/otherview/excel
View做爲視圖接口,AbstractView的render方法建立待渲染的model,調用子類,若是待解析視圖類型是jsp,則調用InternalResourceView,若是是ftl,則調用FreemarkerView,執行渲染
ViewResolver做爲視圖解析接口,主要爲View提供支持,createView方法建立View,buildView根據路徑構造View,併爲View設置一系列屬性
DispatcherServlet根據ModelAndView調用View實現類的render方法進行視圖渲染,ViewResolver實現類起到輔助做用,爲View設置屬性(前綴、後綴等)
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中不免有不足,歡迎指出