Spring源碼解析之MVC篇

什么是MVC?

MVC(Model-View-Controller):它是一種軟件架構(gòu)設(shè)計(jì)模式肝谭,分為三個部分:

  • Model(模型):業(yè)務(wù)的數(shù)據(jù)模型二跋;
  • View(視圖):數(shù)據(jù)模型的可視化裆站;
  • Controller(控制器):模式和視圖的連接控制器溯职。

它的主要目的就是將代碼分層模塊化,降低各層之間的耦合性媒熊,每個模塊符合單一職責(zé)原則奇适。

很多應(yīng)用的Web框架都是基于MVC模式去設(shè)計(jì)的,這里Spring也不例外芦鳍,同樣提供了基于MVC的web框架Spring Web MVC 嚷往,通常我們稱為SpringMVC。

準(zhǔn)備工作

實(shí)際開發(fā)中柠衅,相信我們對SpringMVC的使用已經(jīng)非常熟悉了皮仁,那么在接下來的源碼解析之前,我們先介紹在SpringMVC的一些基礎(chǔ)知識茄茁。

支持的功能

作為Web框架魂贬,SpringMVC也提供了很多豐富的功能:

  • 類型轉(zhuǎn)換:默認(rèn)支持各種數(shù)字和日期類型的數(shù)據(jù)格式化,也支持自定義格式化轉(zhuǎn)化裙顽。
  • 驗(yàn)證:對請求參數(shù)的全局或局部驗(yàn)證付燥,支持JSR-303、HibernateValidator驗(yàn)證愈犹。
  • 攔截器:注冊攔截器對傳入的請求進(jìn)行攔截處理键科。
  • 內(nèi)容類型:自定義請求的內(nèi)容類型解析,像json漩怎、xml等勋颖。
  • 消息轉(zhuǎn)換器:自定義消息轉(zhuǎn)換器對不同類型的消息進(jìn)行序列化和反序列化,默認(rèn)是Jackson勋锤。
  • 視圖控制器:初始化一些默認(rèn)的url請求路徑對應(yīng)的頁面饭玲,像首頁、404叁执、500等茄厘。
  • 視圖解析器:配置視圖的解析器,像Thymeleaf谈宛、Freemarker次哈、velocity等,默認(rèn)使用的是JSP吆录、Jackson窑滞。
  • 靜態(tài)資源:提供一些靜態(tài)資源的url配置。
  • Servlet配置:SpringMVC提供了DispatcherServlet來覆蓋默認(rèn)的DefaultServletHttpRequestHandler處理,特支持自定義的Servlet配置哀卫。
  • 路徑匹配:自定義與路徑匹配和URL處理相關(guān)的選項(xiàng)巨坊。

DispatcherServlet

我們先看它的類圖,

image-20210826105951616

它是前端控制器聊训,是SpringMVC的核心抱究,也是Servlet的實(shí)現(xiàn)子類恢氯,它的主要作用就是處理請求带斑,通過可配置的組件執(zhí)行請求映射,視圖解析勋拟,異常處理等功能勋磕;而我們可以把它當(dāng)作是SpringMVC中真正的Servlet。

Servlet配置

跟IOC敢靡、AOP等一樣挂滓,SpringMVC的Servlet配置同樣支持兩種配置方式,分別是:

  • XML配置:在Servlet3.0之前啸胧,我們通常通過web.xml去配置Servlet赶站,

    <web-app>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app-context.xml</param-value>
        </context-param>
        <servlet>
            <servlet-name>app</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value></param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>app</servlet-name>
            <url-pattern>/app/*</url-pattern>
        </servlet-mapping>
    </web-app>
    

    來完成前端控制器DispatcherServlet的初始化,以及請求映射纺念、視圖解析等其它功能贝椿;包括所有的url映射路徑、攔截器等都配置在xml中陷谱,雖然方便統(tǒng)一管理維護(hù)烙博,但是配置相對繁瑣,不同功能之間高耦合烟逊,也不夠靈活渣窜。

  • Java代碼配置:在Servlet3.0之后的新特性,支持基于注解的配置方式來替代web.xml了宪躯,所以在SpringMVC中我們可以通過Java代碼來配置乔宿,

    public class MyWebApplicationInitializer implements WebApplicationInitializer {
        @Override
        public void onStartup(ServletContext servletContext) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            //注冊配置類
            context.register(AppConfig.class);
            //創(chuàng)建DispatcherServlet
            DispatcherServlet servlet = new DispatcherServlet(context);
            ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("/app/*");
        }
    }
    

    同時也支持與web.xml的結(jié)合使用。目前開發(fā)中最常用的SprongBoot就是依賴于Java配置來配置SpringMVC的访雪。

關(guān)鍵Bean

SpringMVC定義了九種特殊的Bean來完成負(fù)責(zé)處理請求和不同策略的返回渲染详瑞,它們分工明確互不干涉,分別是:

  • MultipartResolver:文件解析器冬阳,用于解析包括文件上傳在內(nèi)的多部分請求的策略蛤虐。
  • LocaleResolver:國際化語言環(huán)境解析器,用于自動解析設(shè)置客戶端的語言環(huán)境肝陪,包括時區(qū)驳庭、請求頭、cookoe、session饲常、區(qū)域的解析蹲堂。
  • ThemeResolver:主題解析器,用于解析自定義靜態(tài)資源的樣式贝淤。
  • HandlerMapping:請求映射器柒竞,負(fù)責(zé)實(shí)際請求的處理器,像配置@RequestMapping注解的類或方法播聪。
  • HandlerAdapter:請求處理適配器朽基,用于請求的解析處理,像參數(shù)適配离陶、反射調(diào)用等稼虎。
  • HandlerExceptionResolver:請求異常解析器,用于解析對請求處理時發(fā)生的異常解決策略招刨,像錯誤響應(yīng)等霎俩,
  • RequestToViewNameTranslator:視圖預(yù)處理轉(zhuǎn)換器,用于獲取Request中的viewName沉眶,將提供的請求轉(zhuǎn)換為默認(rèn)視圖名稱打却。
  • ViewResolver:視圖解析器,將視圖名稱解析為View類型的視圖谎倔。
  • FlashMapManager:用于存儲柳击、檢索和管理 FlashMap實(shí)例,其中FlashMap適用于保存Flash屬性传藏,而Flash屬性用于解決重定向時無法傳遞的參數(shù)的存儲腻暮。

初始化流程

相比較之前解析Spring中IOC、AOP等初始化流程的復(fù)雜毯侦,MVC則更加容易哭靖,可能也是Spring源碼解析中最輕松的一個環(huán)節(jié)了,接下來就讓我們開始吧侈离。

剛才已經(jīng)介紹完SpringMVC中的九種特殊Bean试幽,我們大概知道它們各自的作用,而SpringMVC的初始化流程其實(shí)就和它們一一對應(yīng)相關(guān)卦碾,所以與其說MVC的初始化铺坞,不如說是九種Bean的初始化。

從0到1洲胖,我們還是需要找到初始化流程的入口济榨。前面Servlet配置中已經(jīng)介紹了Servlet的初始化方式了,其中xml的配置是基于XmlWebApplicationContext容器绿映,代碼的配置是基于AnnotationConfigWebApplicationContext容器來加載完成的擒滑;這里我們主要來解析基于代碼的配置方式的初始化流程腐晾。

先寫個非常簡單的測試Demo,

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.scan("com.test.spring.mvc");
//        context.register(MvcConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        return "test";
    }
}

通過idea配置好Tomcat容器丐一,啟動之后訪問http://localhost:8080/test藻糖,看到頁面上成功返回test展示。

接下來我們就開始一步步去探究它的初始化流程了库车。

注冊DispatcherServlet

首先我們需要需要創(chuàng)建IOC容器AnnotationConfigWebApplicationContext巨柒,它是之前的IOC解析中的AnnotationConfigApplicationContext的Web版本;然后我們設(shè)置IOC的包掃描路徑柠衍,主要用來掃描我們編寫的Controller類洋满。

我們知道DispatcherServlet是Servlet的實(shí)現(xiàn)子類,那在了解它之前拧略,我們先了解下Servlet芦岂。

Servlet是運(yùn)行在Web服務(wù)器中的Java程序瘪弓,而它的生命周期如下垫蛆,

image-20210826112440318
  1. Web容器在加載時或者第一次使用Servlet時,先創(chuàng)建Servlet實(shí)例腺怯;
  2. 實(shí)例化之后袱饭,容器會調(diào)用init()方法進(jìn)行初始化,而對于每一個Servlet實(shí)例呛占,init()方法只能被調(diào)用一次虑乖;
  3. 初始化之后,Servlet一直保存在Web容器中晾虑,通過service()方法來處理響應(yīng)客戶端請求疹味;
  4. 銷毀時,先調(diào)用destroy()方法(僅執(zhí)行一次)帜篇,等service()方法中正在執(zhí)行的請求全部完成或超時時糙捺,通過垃圾回收器來銷毀被Web容器釋放的Servlet對象。

接下來這里創(chuàng)建DispatcherServlet對象笙隙,并把IOC容器注冊進(jìn)去洪灯,然后再把DispatcherServlet注冊到容器的Servlet中去,并設(shè)置兩個屬性:

  • setLoadOnStartup:設(shè)置DispatcherServlet的加載順序竟痰,當(dāng)值大于等于0時签钩,表示容器在啟動時就加載并初始化這個Servlet,正數(shù)值越小則加載優(yōu)先級越高坏快;小于0或者不設(shè)置時铅檩,則表示該容器在Servlet被選擇時才會去加載。
  • addMapping:添加url路徑映射莽鸿,在這里可以配置項(xiàng)目接口的url路徑前綴昧旨,默認(rèn)必須要添加/

可以發(fā)現(xiàn)這幾個方法都是調(diào)用Servlet的原生API,而真正的處理代碼都是由Web容器中根據(jù)Servlet的規(guī)范接口去實(shí)現(xiàn)的臼予。而我們最重要還是去關(guān)注Servlet原生API的在SpringMVC中的實(shí)現(xiàn)鸣戴,也就是DispatcherServlet這個類,它也是SpringMVC的核心粘拾。

初始化Servlet

我們已經(jīng)知道Servlet實(shí)例化之后首先會調(diào)用init()方法窄锅,然而我們?nèi)ゲ榭碊ispatcherServlet源碼,并沒有發(fā)現(xiàn)這個方法缰雇,那么這個方法的具體實(shí)現(xiàn)肯定是在其某個父類當(dāng)中入偷,通過它的類圖,我們先查看頂層父類接口Servlet的源碼械哟,

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

發(fā)現(xiàn)init()的方法是由子類GenericServlet實(shí)現(xiàn)疏之,

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    ...
        
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }
        
    ...
}

而這里又調(diào)用了自定義的一個init()方法鼠锈,而它的具體實(shí)現(xiàn)實(shí)際是委托了子類HttpServletBean來完成拘悦,我們看下實(shí)現(xiàn)源碼,

    public final void init() throws ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
        }
        //init-param設(shè)置的Bean屬性
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                //封裝成IOC容器中的BeanWrapper
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                //屬性注入
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }
                throw var4;
            }
        }
        //初始化
        this.initServletBean();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
        }

    }

我們主要關(guān)注調(diào)用initServletBean()方法來進(jìn)行Servlet的初始化姥宝,而具體的方法實(shí)現(xiàn)是由FrameworkServlet來完成的爸业,

   protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();
        try {
            this.webApplicationContext = this.initWebApplicationContext();
            //空實(shí)現(xiàn)其骄,子類擴(kuò)展接口
            this.initFrameworkServlet();
        } catch (ServletException var5) {
            this.logger.error("Context initialization failed", var5);
            throw var5;
        } catch (RuntimeException var6) {
            this.logger.error("Context initialization failed", var6);
            throw var6;
        }
        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms");
        }
    }

通過源碼,我們看到它的作用主要就是為了調(diào)用initWebApplicationContext()方法來初始化WebApplicationContext扯旷,而我們知道WebApplicationContext實(shí)例在注冊DispatcherServlet前已經(jīng)完成創(chuàng)建了拯爽,

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    //配置和刷新ApplicationContext
                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            //加載ApplicationContext
            wac = this.findWebApplicationContext();
        }
        if (wac == null) {
            //創(chuàng)建ApplicationContext
            wac = this.createWebApplicationContext(rootContext);
        }
        if (!this.refreshEventReceived) {
            this.onRefresh(wac);
        }
        if (this.publishContext) {
            //緩存到屬性
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
            }
        }
        return wac;
    }

通過源碼能看到這里初始化WebApplicationContext的流程邏輯如下:

  1. webApplicationContext存在,由于已經(jīng)通過構(gòu)造函數(shù)注入钧忽,則建立父子容器關(guān)系毯炮,調(diào)用configureAndRefreshWebApplicationContext()初始化容器(最終調(diào)用refresh()方法來完成);
  2. webApplicationContext不存在耸黑,先嘗試從ServletContext的屬性緩存中加載桃煎,如果加載不到,則調(diào)用createWebApplicationContext()來默認(rèn)創(chuàng)建WebApplicationContext實(shí)例崎坊,并建立父子容器關(guān)系备禀,調(diào)用configureAndRefreshWebApplicationContext()初始化容器;
  3. 調(diào)用onRefresh()方法初始化MVC組件奈揍;
  4. 將webApplicationContext緩存到ServletContext的屬性中曲尸。

到這里的方法執(zhí)行完,也就完成了Servlet男翰,也就是DispatcherServlet的初始化了另患,也代表著Web容器已經(jīng)啟動完成了。

初始化相關(guān)組件

我們知道DispatcherServlet是SpringMVC的核心蛾绎,其中封裝了MVC中的各種組件昆箕,那接下來我們就具體地看看上面調(diào)用onRefresh()方法中是怎么完成對MVC組件的初始化的鸦列?

首先注意到onRefresh()方法是FrameworkServlet委托子類DispatcherServlet來實(shí)現(xiàn)的,看下源碼鹏倘,

    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

這里的實(shí)現(xiàn)代碼分別封裝了各組件的初始化方法薯嗤,按順序分別是:

  • 多文件上傳初始化
  • 語言環(huán)境解析初始化
  • 主題解析器初始化
  • 請求映射初始化
  • 請求適配器初始化
  • 請求異常解析初始化
  • 視圖預(yù)處理器初始化
  • 視圖解析器器初始化
  • FlashMap管理初始化

如果點(diǎn)進(jìn)去查看它們各自的初始化邏輯會發(fā)現(xiàn)很簡單,其實(shí)就是對九種關(guān)鍵Bean的實(shí)例化纤泵,其中一些組件在沒有配置的情況下骆姐,會使用默認(rèn)的配置去解析處理,而它們各自的作用前面也已經(jīng)介紹過了捏题,這里就不在一一分析各組件初始化方法的源碼了玻褪。

調(diào)用流程

經(jīng)過上面的初始化流程已經(jīng)成功完成Web容器的啟動了,那么接下來我們思考下公荧,當(dāng)服務(wù)端接收到客戶端的請求時带射,SpringMVC是怎么對請求進(jìn)行解析處理的呢?

首先循狰,回到Servlet的生命周期窟社,我們知道Servlet會一直存在Web容器中,然后通過service()方法來處理響應(yīng)客戶端請求晤揣,那我們就從這個入口開始分析桥爽。

1.開始解析

通過DispatcherServlet的類圖,查看頂層父類接口Servlet的service()方法昧识,發(fā)現(xiàn)被子類FrameworkServlet覆寫,

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (HttpMethod.PATCH != httpMethod && httpMethod != null) {
            super.service(request, response);
        } else {
            this.processRequest(request, response);
        }
    }

當(dāng)請求不為空同時方式不為PATCH時盗扒,會調(diào)用父類HttpServlet的service()方法跪楞,

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

發(fā)現(xiàn)這是其實(shí)就是對不同請求方式的處理方法進(jìn)行路由,而doGet()侣灶、doPost()甸祭、doPut()等處理方法實(shí)際還是交給FrameworkServlet來實(shí)現(xiàn),而最終所有的請求處理都是交給processRequest()來完成的褥影,

    ...

    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
    
    ...
        
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //用于統(tǒng)計(jì)請求的處理時間
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        //保留請求的快照池户,語言環(huán)境、屬性等
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
        this.initContextHolders(request, localeContext, requestAttributes);
        try {
            //請求處理
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            //恢復(fù)原始屬性
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            if (this.logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", (Throwable)failureCause);
                } else if (asyncManager.isConcurrentHandlingStarted()) {
                    this.logger.debug("Leaving response open for concurrent processing");
                } else {
                    this.logger.debug("Successfully completed request");
                }
            }
            //發(fā)布請求處理事件
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }

    }

分析源碼知道凡怎,這里主要是對請求前后的準(zhǔn)備和事件處理工作校焦,為了保證請求前后的原始屬性不變;而具體的細(xì)節(jié)處理都是委托子類DispatcherServlet的doService()方法來完成的统倒,

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();
            label112:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label112;
                    }
                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        if (this.flashMapManager != null) {
            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);
        }
        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

實(shí)現(xiàn)邏輯比較簡單寨典,主要作用還是為了請求的處理而做準(zhǔn)備,將MVC中初始化的相關(guān)組件配置保存中請求的屬性中房匆,以便后面的解析工作耸成;更詳細(xì)的解析處理還是通過封裝的doDispatch()方法完成的报亩,

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;
                try {
                    //多部分請求檢查轉(zhuǎn)換
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //獲取請求對應(yīng)的Handler
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //獲取HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        //Last-Modified緩存機(jī)制
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    //前置處理
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    //處理請求
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    //應(yīng)用默認(rèn)視圖名稱
                    this.applyDefaultViewName(processedRequest, mv);
                    //后置處理
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                //結(jié)果處理
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
        }
    }

經(jīng)過層層的準(zhǔn)備,具體的請求的解析處理邏輯終于展現(xiàn)出來了井氢,而之前初始化的相關(guān)組件的作用也在這里得到了體現(xiàn)弦追。接下來會分別解析源碼的具體處理邏輯。

2.多部分請求轉(zhuǎn)換

開始會先調(diào)用checkMultipart()方法來檢查當(dāng)前request是否需要轉(zhuǎn)換為包含文件上傳在內(nèi)的多部分請求MultipartHttpServletRequest花竞,進(jìn)去看下源碼骗卜,

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");
            } else if (this.hasMultipartException(request)) {
                this.logger.debug("Multipart resolution failed for current request before - skipping re-resolution for undisturbed error rendering");
            } else {
                try {
                    return this.multipartResolver.resolveMultipart(request);
                } catch (MultipartException var3) {
                    if (request.getAttribute("javax.servlet.error.exception") == null) {
                        throw var3;
                    }
                }
                this.logger.debug("Multipart resolution failed for error dispatch", var3);
            }
        }
        return request;
    }

這里如果之前沒有配置multipartResolver解析器,則這里會跳過檢查左胞;反之則會調(diào)用isMultipart()判斷當(dāng)前請求是否多部分請求寇仓,如果是,則最后會通過MultipartResolver解析器調(diào)用resolveMultipart()將當(dāng)前request轉(zhuǎn)換為MultipartHttpServletRequest烤宙,查看它的類圖遍烦,會發(fā)現(xiàn)它其實(shí)是HttpServletRequest的擴(kuò)展子類;而resolveMultipart()中轉(zhuǎn)換處理的源碼也很復(fù)雜躺枕,感興趣可以深究服猪。

image-20210909152641049

3.獲取請求對應(yīng)的Handler

檢查完request之后,然后會調(diào)用getHandler()方法獲取當(dāng)前request對應(yīng)的處理器拐云,也就是請求路徑對應(yīng)的controller罢猪,我們來看下是怎么去尋找獲取到的,

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();
            while(var2.hasNext()) {
                HandlerMapping hm = (HandlerMapping)var2.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

這里handlerMappings是個List<HandlerMapping>集合叉瘩,在初始化的時候會加載兩種Url映射器:

  • BeanNameUrlHandlerMapping:匹配BeanName為路徑的Controller膳帕,比如BeanName="/test";雖然這樣的url配置在SpringMVC中是支持的薇缅,但是實(shí)際開發(fā)中是不會出現(xiàn)的危彩。
  • RequestMappingHandlerMapping:匹配@RequestMapping,包括@GetMapping泳桦、@PostMapping等注解設(shè)置的路徑汤徽。

這里我們還是以正常的設(shè)置方式RequestMappingHandlerMapping去解析url是怎么匹配到Controller的,我們看下getHandler()源碼灸撰,

    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //獲取對應(yīng)的HandlerMethod
        Object handler = this.getHandlerInternal(request);
        if (handler == null) {
            handler = this.getDefaultHandler();
        }
        if (handler == null) {
            return null;
        } else {
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.obtainApplicationContext().getBean(handlerName);
            }
            //封裝到執(zhí)行鏈中
            HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
            //跨域處理
            if (CorsUtils.isCorsRequest(request)) {
                CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
                CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
                CorsConfiguration config = globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig;
                executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
            }
            return executionChain;
        }
    }

它的實(shí)現(xiàn)是在HandlerMapping的實(shí)現(xiàn)子類AbstractHandlerMapping中谒府,這里會先調(diào)用getHandlerInternal()匹配對應(yīng)的Controller,并封裝成HandlerMethod返回浮毯;然后調(diào)用getHandlerExecutionChain()完疫,將當(dāng)前request和HandlerMethod封裝到執(zhí)行鏈HandlerExecutionChain中,并將匹配的攔截器HandlerInterceptor添加到執(zhí)行鏈里亲轨;最后判斷當(dāng)前request是否為跨域請求趋惨,是則再次處理封裝執(zhí)行鏈HandlerExecutionChain。

這里利用責(zé)任鏈的處理模式惦蚊,降低請求對象與處理器的耦合器虾,可以方便的對請求解析進(jìn)行擴(kuò)展和攔截讯嫂。

我們來看子類AbstractHandlerMethodMapping中g(shù)etHandlerInternal()的具體實(shí)現(xiàn),

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //截取有效的url路徑
        String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Looking up handler method for path " + lookupPath);
        }
        //獲取讀鎖
        this.mappingRegistry.acquireReadLock();
        HandlerMethod var4;
        try {
            //通過路徑查找HandlerMethod
            HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
            if (this.logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    this.logger.debug("Returning handler method [" + handlerMethod + "]");
                } else {
                    this.logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            //從容器中獲取Controller對象并封裝成HandlerMethod
            var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
        } finally {
            //釋放讀鎖
            this.mappingRegistry.releaseReadLock();
        }
        return var4;
    }

通過上面代碼兆沙,可以看到這里的url匹配查找會先加上讀鎖欧芽,我們知道讀鎖是共享的,而寫鎖是獨(dú)占的葛圃,主要用來保證容器中注冊的映射發(fā)生改變時千扔,不會影響與對應(yīng)的Controller的一致性。我們看下匹配方法lookupHandlerMethod()的源碼库正,

    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
        //獲取已注冊的路徑
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            //匹配Controller并添加到matches中
            this.addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        if (!matches.isEmpty()) {
            //對HandlerMethodMapping排序
            Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
            Collections.sort(matches, comparator);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
            }
            AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
                //匹配多個相同handler則拋出異常
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                }
            }
            this.handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        } else {
            return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

    private void addMatchingMappings(Collection<T> mappings, List<AbstractHandlerMethodMapping<T>.Match> matches, HttpServletRequest request) {
        Iterator var4 = mappings.iterator();
        while(var4.hasNext()) {
            T mapping = var4.next();
            //匹配Mapping
            T match = this.getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new AbstractHandlerMethodMapping.Match(match, (HandlerMethod)this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }

到這里基本就完成url路徑匹配了曲楚,在這些方法中做了很多準(zhǔn)備及匹配處理,看起來實(shí)現(xiàn)很復(fù)雜褥符,但是慢慢Debug下去龙誊,會發(fā)現(xiàn)整個邏輯的主要流程還是比較簡單的,更詳細(xì)的匹配邏輯這里就不再深入了喷楣。

如果加載當(dāng)前請求對應(yīng)的Handler不存在的話趟大,服務(wù)端則會響應(yīng)404錯誤返回。

4.獲取HandlerAdapter

找到對應(yīng)的Handler之后铣焊,需要通過調(diào)用getHandlerAdapter()拿到Handler對應(yīng)的HandlerAdapter逊朽,而它的作用前面我們也已經(jīng)知道了,

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();
            while(var2.hasNext()) {
                HandlerAdapter ha = (HandlerAdapter)var2.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing handler adapter [" + ha + "]");
                }
                if (ha.supports(handler)) {
                    return ha;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

代碼比較簡單曲伊,初始化時handlerAdapters會加載以下三種HandlerAdapter:

  • HttpRequestHandlerAdapter:適配實(shí)現(xiàn)HttpRequestHandler接口的Handler叽讳,需要重寫handleRequest方法 。
  • SimpleControllerHandlerAdapter:適配實(shí)現(xiàn)Controller接口的Handler熊昌,需要重寫handleRequest方法绽榛,并返回ModelAndView。
  • RequestMappingHandlerAdapter:和RequestMappingHandlerMapping配對使用婿屹,適配@RequestMapping等注解的Handler。

這里通過supports()方法進(jìn)行instanceof類型判斷推溃,來選擇相應(yīng)的HandlerAdapter進(jìn)行后續(xù)的處理昂利。

5.LastModified緩存機(jī)制

接下來針對GET或HEAD的請求方式,這里做了一個叫LastModified的緩存機(jī)制铁坎,它的作用及實(shí)現(xiàn)邏輯也很好理解蜂奸,首先第一次請求成功時,服務(wù)端會在響應(yīng)頭中添加Last-Modified屬性硬萍,值為服務(wù)端最后的更新時間扩所;當(dāng)請求第二次訪問時,會去調(diào)用getLastModified()方法獲取請求頭中If-Modified-Since屬性朴乖,然后調(diào)用checkNotModified()方法檢查服務(wù)端的內(nèi)容在屬性值的時間之后是否發(fā)生改變祖屏,如果未發(fā)生變化則響應(yīng)304狀態(tài)碼(只返回響應(yīng)頭助赞,不然會響應(yīng)內(nèi)容)。

6.攔截器的前后置處理

和Spring中的BeanPostProcessor中相似袁勺,SpringMVC在這里提供了HandlerInterceptor攔截器雹食,針對在Handler真正處理請求的邏輯前后,可以方便擴(kuò)展對請求的一些處理期丰,我們看下HandlerInterceptor源碼群叶,

public interface HandlerInterceptor {
    //處置處理
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    //后置處理
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
    //完成后處理
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

它提供了三種方法的接口實(shí)現(xiàn),開發(fā)中我們可以通過實(shí)現(xiàn)此接口來對Request做一些攔截處理钝荡。

7.處理請求

前面我們已經(jīng)獲取了當(dāng)前請求對應(yīng)的Handler及做了一些處理前的準(zhǔn)備工作街立,而真正處理請求是通過handle()方法來完成的,這里會調(diào)用AbstractHandlerMethodAdapter中的源碼埠通,

    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return this.handleInternal(request, response, (HandlerMethod)handler);
    }

發(fā)現(xiàn)具體實(shí)現(xiàn)還是在子類RequestMappingHandlerAdapter的handleInternal()中赎离,

    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);
        ModelAndView mav;
        //會話同步
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized(mutex) {
                    //處理邏輯
                    mav = this.invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                //處理邏輯
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            //處理邏輯
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }
        if (!response.containsHeader("Cache-Control")) {
           //會話緩存
            if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                this.prepareResponse(response);
            }
        }
        return mav;
    }

我們能看到這里做了關(guān)于Session會話的同步及緩存,但是我們主要關(guān)注的是調(diào)用invokeHandlerMethod()來真正處理請求的植阴,如果繼續(xù)深入了解該方法的源碼蟹瘾,能夠知道會先對方法帶有的參數(shù)進(jìn)行解析適配等工作,最后底層還是通過反射來調(diào)用我們之前拿到的Handler中保存的Controller類及方法掠手,也就是我們的自己實(shí)現(xiàn)的業(yè)務(wù)邏輯代碼了憾朴。

8.異常處理

上面的處理調(diào)用完成會返回一個ModelAndView,如果我們服務(wù)響應(yīng)的是json喷鸽、xml等非頁面視圖模型這樣的格式众雷,這個的mv就等于null;最后會通過調(diào)用processDispatchResult()方法對ModelAndView進(jìn)行處理做祝,

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }
        if (mv != null && !mv.wasCleared()) {
            this.render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
        }
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
            }

        }
    }

這里的結(jié)果處理會分為兩個步驟砾省,第一個就是調(diào)用processHandlerException()方法對處理請求的邏輯中發(fā)生的異常進(jìn)行處理,

    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers != null) {
            Iterator var6 = this.handlerExceptionResolvers.iterator();
            while(var6.hasNext()) {
                HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
                exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        if (exMv != null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            } else {
                if (!exMv.hasView()) {
                    String defaultViewName = this.getDefaultViewName(request);
                    if (defaultViewName != null) {
                        exMv.setViewName(defaultViewName);
                    }
                }
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
                }
                WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
                return exMv;
            }
        } else {
            throw ex;
        }
    }

    @Nullable
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        if (this.shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
            }
            this.prepareResponse(ex, response);
            ModelAndView result = this.doResolveException(request, response, handler, ex);
            if (result != null) {
                this.logException(ex, request);
            }
            return result;
        } else {
            return null;
        }
    }

我們能看到混槐,最后將發(fā)生的異常信息编兄、狀態(tài)碼等寫到Response中返回給客戶端。

9.視圖渲染

如果當(dāng)前請求處理結(jié)果返回的ModelAndView存在声登,則會調(diào)用render()方法進(jìn)行頁面渲染狠鸳,

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
        response.setLocale(locale);
        //獲取視圖名稱
        String viewName = mv.getViewName();
        View view;
        if (viewName != null) {
            //視圖解析
            view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
            }
        } else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
            }
        }
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
        }
        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }
            //視圖渲染
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var8) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var8);
            }
            throw var8;
        }
    }

我們先會按到返回的視圖名稱,然后通過ViewResolver視圖解析器去解析獲取對應(yīng)的View悯嗓,最后再調(diào)用render()對頁面進(jìn)行渲染返回件舵,向頁面中的JSTL語法、EL表達(dá)式或者原始的Request的屬性等都會進(jìn)行解析脯厨。

最后到這一步铅祸,SpringMVC中的調(diào)用流程處理就已經(jīng)全部完成了。


把一件事做到極致就是天分合武!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末临梗,一起剝皮案震驚了整個濱河市涡扼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夜焦,老刑警劉巖壳澳,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茫经,居然都是意外死亡巷波,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門卸伞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抹镊,“玉大人,你說我怎么就攤上這事荤傲】宥” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵遂黍,是天一觀的道長终佛。 經(jīng)常有香客問我,道長雾家,這世上最難降的妖魔是什么铃彰? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮芯咧,結(jié)果婚禮上牙捉,老公的妹妹穿的比我還像新娘。我一直安慰自己敬飒,他們只是感情好邪铲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著无拗,像睡著了一般带到。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上英染,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天阴孟,我揣著相機(jī)與錄音,去河邊找鬼税迷。 笑死,一個胖子當(dāng)著我的面吹牛锹漱,可吹牛的內(nèi)容都是我干的箭养。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼哥牍,長吁一口氣:“原來是場噩夢啊……” “哼毕泌!你這毒婦竟也來了喝检?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤撼泛,失蹤者是張志新(化名)和其女友劉穎挠说,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愿题,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡损俭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了潘酗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杆兵。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仔夺,靈堂內(nèi)的尸體忽然破棺而出琐脏,到底是詐尸還是另有隱情,我是刑警寧澤缸兔,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布日裙,位于F島的核電站,受9級特大地震影響惰蜜,放射性物質(zhì)發(fā)生泄漏昂拂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一蝎抽、第九天 我趴在偏房一處隱蔽的房頂上張望政钟。 院中可真熱鬧,春花似錦樟结、人聲如沸养交。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碎连。三九已至,卻和暖如春驮履,著一層夾襖步出監(jiān)牢的瞬間鱼辙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工玫镐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倒戏,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓恐似,卻偏偏與公主長得像杜跷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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