DispatchServlet視圖解析過程分析

DispatchServlet視圖解析過程分析

image.png

以GET方法說明

org.springframework.web.servlet.FrameworkServlet:
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);
    
    // 上面都是一些準(zhǔn)備工作,重點看doService方法;doService是一個抽象方法,由子類實現(xiàn)并處理request請求
    try {
        doService(request, response);
    }
    ...
    // 省略了catch和finally相關(guān)代碼
}

下面分析FrameworkServlet的實現(xiàn)類DispatcherServlet的doService方法

org.springframework.web.servlet.DispatcherServlet:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    
    // 上面設(shè)置request的一些屬性秒裕,重點看doDispatch方法
    try {
        doDispatch(request, response);
    }
    ...
    // 省略了catch和finally相關(guān)代碼
}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 從handlerMappings找到處理request的處理器handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                // 沒有找到對應(yīng)的處理器母市,返回404狀態(tài)
                noHandlerFound(processedRequest, response);
                return;
            }

            // 從handlerAdapters找到支持handler的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            
            // 請求處理之前垢箕,調(diào)用攔截器的preHandle方法預(yù)處理充边,一般是權(quán)限驗證,如果返回false蒜茴,結(jié)束請求
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 調(diào)用AnnotationMethodHandlerAdapter的handler方法處理request請求,返回ModelAndView
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 請求處理之后渲染頁面之前浆西,調(diào)用攔截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        
        // 處理handler返回的結(jié)果粉私,即渲染頁面
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    ...
    // 省略catch和finally代碼塊
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    boolean errorView = false;

    // 處理異常情況,自定義的異常處理類可以參考DefaultHandlerExceptionResolver
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // 渲染頁面近零,通過視圖名稱進行視圖解析
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                    "': assuming HandlerAdapter completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }
    
    // 調(diào)用攔截器的afterCompletion毡鉴,一般是處理資源回收操作
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    // 根據(jù)視圖名稱解析成視圖對象崔泵,例如:視圖解析器FreeMarkerViewResolver將視圖名解析成FreeMarkerView并完成FreeMarkerView的初始化操作,設(shè)置FreeMarkerConfig
    View view;
    if (mv.isReference()) {
        // We need to resolve the view name.
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
        // 視圖對象執(zhí)行渲染操作
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

下面解析視圖渲染過程猪瞬,實際是renderMergedOutputModel方法進行渲染憎瘸,這是一個抽象方法,由子類實現(xiàn)

org.springframework.web.servlet.view.AbstractView:
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);
}

protected abstract void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

AbstractTemplateView是AbstractView的一個子類陈瘦,實現(xiàn)了renderMergedOutputModel方法

org.springframework.web.servlet.view.AbstractTemplateView:
protected final void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    ...

    // 將RequestContext添加到model中幌甘,默認(rèn)添加
    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));
    }

    // 設(shè)置response的contentType
    applyContentType(response);

    // 抽象方法,由子類實現(xiàn)
    renderMergedTemplateModel(model, request, response);
}

protected abstract void renderMergedTemplateModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

重點來了痊项,這里引出FreeMarkerView锅风,它是AbstractTemplateView的一個子類

org.springframework.web.servlet.view.freemarker.FreeMarkerView:
protected void renderMergedTemplateModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // 可以在這里執(zhí)行一些幫助操作,F(xiàn)reeMarkerView中此方法為空
    exposeHelpers(model, request);
    
    // FreeMarkerView執(zhí)行渲染
    doRender(model, request, response);
}

protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 這里遍歷model中的key-value鞍泉,添加到request的屬性中
    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);
    
    // 將FreeMarker模板處理結(jié)果響應(yīng)到response
    processTemplate(getTemplate(locale), fmModel, response);
}

protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response)
        throws IOException, TemplateException {

    template.process(model, response.getWriter());
}

接下來就看到FreeMarker核心的API了皱埠,通過Configuration創(chuàng)建template

Configuration cfg = new Configuration();
...
Template myTemplate = cfg.getTemplate("myTemplate.html");
freemarker.template.Template:
public void process(Object rootMap, Writer out)
throws TemplateException, IOException
{
    createProcessingEnvironment(rootMap, out, null).process();
}
freemarker.core.Environment
public void process() throws TemplateException, IOException {
    Object savedEnv = threadEnv.get();
    threadEnv.set(this);
    try {
        // Cached values from a previous execution are possibly outdated.
        clearCachedValues();
        try {
            // 自動導(dǎo)入freemarker.properties中的auto_import和auto_include
            doAutoImportsAndIncludes(this);
            visit(getTemplate().getRootTreeNode());
            // It's here as we must not flush if there was an exception.
            if (getAutoFlush()) {
                out.flush();
            }
        } finally {
            // It's just to allow the GC to free memory...
            clearCachedValues();
        }
    } finally {
        threadEnv.set(savedEnv);
    }
}

附錄

一個FreeMarker解析ftl模板的示例,其中咖驮,hello.ftl是ftl模板边器,hello.html是最終解析成html的文件

Configuration configuration = new Configuration();
File outFile = null;
try {
    configuration.setDirectoryForTemplateLoading(new File("/WEB-INF/ftl/template"));
    Template template = configuration.getTemplate("hello.ftl");

    outFile = new File("hello.html");
    FileOutputStream fileOutputStream = new FileOutputStream(outFile);
    OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, "UTF-8");

    MapModel mapModel = new MapModel(dataMap, new BeansWrapper());
    template.process(mapModel, writer);
    writer.close();
    fileOutputStream.close();
} catch (Exception e) {
    LOGGER.info(e.getMessage(), e);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市托修,隨后出現(xiàn)的幾起案子忘巧,更是在濱河造成了極大的恐慌,老刑警劉巖睦刃,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砚嘴,死亡現(xiàn)場離奇詭異,居然都是意外死亡涩拙,警方通過查閱死者的電腦和手機际长,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兴泥,“玉大人工育,你說我怎么就攤上這事∮羟幔” “怎么了翅娶?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長好唯。 經(jīng)常有香客問我竭沫,道長,這世上最難降的妖魔是什么骑篙? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任蜕提,我火速辦了婚禮,結(jié)果婚禮上靶端,老公的妹妹穿的比我還像新娘谎势。我一直安慰自己凛膏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布脏榆。 她就那樣靜靜地躺著猖毫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪须喂。 梳的紋絲不亂的頭發(fā)上吁断,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音坞生,去河邊找鬼仔役。 笑死,一個胖子當(dāng)著我的面吹牛是己,可吹牛的內(nèi)容都是我干的又兵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼卒废,長吁一口氣:“原來是場噩夢啊……” “哼沛厨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起升熊,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤俄烁,失蹤者是張志新(化名)和其女友劉穎绸栅,沒想到半個月后级野,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡粹胯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年蓖柔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片风纠。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡况鸣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竹观,到底是詐尸還是另有隱情镐捧,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布臭增,位于F島的核電站懂酱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏誊抛。R本人自食惡果不足惜列牺,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拗窃。 院中可真熱鬧瞎领,春花似錦泌辫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至驼修,卻和暖如春澜搅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邪锌。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工勉躺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人觅丰。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓饵溅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妇萄。 傳聞我的和親對象是個殘疾皇子蜕企,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容