由於前段時間一直在成都出差加上心情比較亂,很長時間沒有更新博客。
最近一個人負責的成都項目進展比較順利,基本只剩下一些故障單的修復,所以壓力不是很大,所以近段時間就是解決一下故障單,然后剩下的就是為主體項目4.0部分前端資源如何整合進行學習和思考,初步打算是使用requireJS,AMD標准的JS模塊化還是比較適合前台的加載,這些容后續再表。
一直對框架上對模版的加載比較好奇,所以今天就看了下部分源碼,以下內容如有理解不當或者錯誤,歡迎指正。
java web容器,由服務器向瀏覽器客戶端寫回頁面無非就是向response中的outputstream流中寫入內容。而我們使用各種各樣的模板時(比如freemarker或者jsp),首先需要在spring的主要的dispatchServlet對於的配置文件中配置模板解析的bean。常見的配置如下:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8" /> <!-- 文件大小最大值 --> <property name="maxUploadSize" value="10485760000" /> <!-- 內存中的最大值 --> <property name="maxInMemorySize" value="40960" /> </bean>
當我們對SpringMVC控制的資源發起請求時,這些請求都會被SpringMVC 的DispatcherServlet處理,接着Spring會分析看哪一個HandlerMapping定義的所有請求映射中存 在對該請求的最合理的映射。然后通過該HandlerMapping取得其對應的Handler,接着再通過相應 的HandlerAdapter處理該Handler。HandlerAdapter在對Handler進行處理之后會返回一個 ModelAndView對象。在獲得了ModelAndView對象之后,Spring就需要把該View渲染給用戶,即返 回給瀏覽器。在這個渲染的過程中,發揮作用的就是ViewResolver和View。當Handler返回的 ModelAndView中不包含真正的視圖,只返回一個邏輯視圖名稱的時候,ViewResolver就會把該邏 輯視圖名稱解析為真正的視圖View對象。View是真正進行視圖渲染,把結果返回給瀏覽器的。
ViewResolver和View介紹 SpringMVC用於處理視圖最重要的兩個接口是ViewResolver和View。ViewResolver的主要作用 是把一個邏輯上的視圖名稱解析為一個真正的視圖,SpringMVC中用於把View對象呈現給客戶端的 是View對象本身,而ViewResolver只是把邏輯視圖名稱解析為對象的View對象。View接口的主要 作用是用於處理視圖,然后返回給客戶端。
view接口中聲明了如下的方法
/** * Render the view given the specified model. * <p>The first step will be preparing the request: In the JSP case, * this would mean setting model objects as request attributes. * The second step will be the actual rendering of the view, * for example including the JSP via a RequestDispatcher. * @param model Map with name Strings as keys and corresponding model * objects as values (Map can also be {@code null} in case of empty model) * @param request current HTTP request * @param response HTTP response we are building * @throws Exception if rendering failed */ void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
即實現該方法即可。(在dispatchServlet的doservice方法中調用了私有方法processDispatchResult, 該私有方法中調用了render(mv, request, response) ,此處的mv為controller層返回的ModelAndView類的一個實例,在該render方法中有一行view.render(mv.getModelInternal(), request, response);,即在該方法中會實現寫入response中)。
我們隨意找一個實現 FreeMarkerView (public class FreeMarkerView extends AbstractTemplateView , AbstractTemplateView extends AbstractUrlBasedView , AbstractUrlBasedView extends AbstractView implements InitializingBean ,
class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware 可見一個view還是很復雜的,隱藏的這么深。)
我們發現在AbstractView 類中實現了render方法
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes); } Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
createMergedOutputModel方法主要是將一些屬性填充到Map中,prepareResponse內容如下,主要是對response頭進行了一些屬性設置
/** * Prepare the given response for rendering. * <p>The default implementation applies a workaround for an IE bug * when sending download content via HTTPS. * @param request current HTTP request * @param response current HTTP response */ protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { if (generatesDownloadContent()) { response.setHeader("Pragma", "private"); response.setHeader("Cache-Control", "private, must-revalidate"); } }
重點看renderMergedOutputModel函數,在AbstractTemplateView類中對該方法進行了實現
protected final void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (this.exposeRequestAttributes) { for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) { String attribute = en.nextElement(); if (model.containsKey(attribute) && !this.allowRequestOverride) { throw new ServletException("Cannot expose request attribute '" + attribute + "' because of an existing model object of the same name"); } Object attributeValue = request.getAttribute(attribute); if (logger.isDebugEnabled()) { logger.debug("Exposing request attribute '" + attribute + "' with value [" + attributeValue + "] to model"); } model.put(attribute, attributeValue); } } if (this.exposeSessionAttributes) { HttpSession session = request.getSession(false); if (session != null) { for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) { String attribute = en.nextElement(); if (model.containsKey(attribute) && !this.allowSessionOverride) { throw new ServletException("Cannot expose session attribute '" + attribute + "' because of an existing model object of the same name"); } Object attributeValue = session.getAttribute(attribute); if (logger.isDebugEnabled()) { logger.debug("Exposing session attribute '" + attribute + "' with value [" + attributeValue + "] to model"); } model.put(attribute, attributeValue); } } } if (this.exposeSpringMacroHelpers) { if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) { throw new ServletException( "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + "' because of an existing model object of the same name"); } // Expose RequestContext instance for Spring macros. model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, new RequestContext(request, response, getServletContext(), model)); } applyContentType(response); renderMergedTemplateModel(model, request, response); }
注意最后一行的renderMergedTemplateModel函數,該函數在FreeMarkerView 類中進行了實現,調用了doRender方法
protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose model to JSP tags (as request attributes). exposeModelAsRequestAttributes(model, request); // Expose all standard FreeMarker hash models. SimpleHash fmModel = buildTemplateModel(model, request, response); if (logger.isDebugEnabled()) { logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'"); } // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); processTemplate(getTemplate(locale), fmModel, response); }
其中processTemplate(getTemplate(locale), fmModel, response);中只有一行 template.process(model, response.getWriter());
關於freemarker的jar包中的
這是一個很經典的模板引擎的執行流程:
- Configuration可以理解為一個工廠,它負責產生一個對外接口Template類。它首先會從cache中查找是否已經有編譯好的Template,如果不存在,則對模板進行編譯。
- Template實際上是一個帶執行語義的語法樹,樹的節點是TemplateObject。
- FMParser是javacc生成的語法解析類,它最終輸出是以FMParser.root()為根的語法樹。
- dataModel是外部對模板引擎的數據輸入,它會被轉化為TemplateModel,並代入模板的渲染過程。
- 最后的步驟是根據數據,遍歷編譯好的語法樹,並輸出結果,這一步的入口時TemplateElement.accept()。
以上即是所有的分析。
下一篇應該會寫freemarker其中的標簽是如何解析的