SpringMVC源碼學習:容器初始化+MVC初始化+請求分發處理+參數解析+返回值解析+視圖解析

一、前言

版本:

springMVC 5.0.2RELEASE

JDK1.8

前端控制器的配置:

web.xml

    <!--配置前端控制器-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--加載類路徑下的配置文件-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--服務器啟動時創建對象,值越小,優先級越高,越先創建對象-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--注意不是/*,而是,因為/*還會攔截*.jsp等請求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

springmvc.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:mvc="//www.springframework.org/schema/mvc"
       xmlns:context="//www.springframework.org/schema/context"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        //www.springframework.org/schema/beans
        //www.springframework.org/schema/beans/spring-beans.xsd
        //www.springframework.org/schema/mvc
        //www.springframework.org/schema/mvc/spring-mvc.xsd
        //www.springframework.org/schema/context
        //www.springframework.org/schema/context/spring-context.xsd">
    <!-- 開啟註解掃描 -->
    <context:component-scan base-package="com.smday"/>

    <!-- 視圖解析器對象 -->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 開啟SpringMVC框架註解的支持 -->
    <mvc:annotation-driven/>
    <!--放行靜態資源-->
    <mvc:default-servlet-handler/>

</beans>

二、初始化

在這裡插入圖片描述

DispatcherServlet的啟動與Servlet的啟動過程緊密聯繫,我們通過以上繼承圖就可以發現。

1. 容器初始化

Servlet中定義的init()方法就是其生命周期的初始化方法,接着往下走,GenericServlet並沒有給出具體實現,在HttpServletBean中的init()方法給出了具體的實現:

HttpServletBean.init()方法(忽略日誌)

	@Override
	public final void init() throws ServletException {
        //根據初始化參數設置bean屬性(我們設置了contextConfigLocation,故可以獲取)
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                //包裝DispatcherServlet
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                //獲取資源加載器,用以加載springMVC的配置文件
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                //註冊一個ResourceEditor
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                //該方法為空實現,可以重寫,初始化BeanWrapper
				initBeanWrapper(bw);
                //最終將init-param讀取的值spirng-mvc.xml存入contextConfigLocation中
				bw.setPropertyValues(pvs, true);
			}
		}

		// 讓子類實現初始化
		initServletBean();

	}

那就來看看FrameworfServlet.initServletBean()幹了啥(基本都是日誌記錄,還有計時,省略了這些部分):

	/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
        //WebApplicationContext的初始化
        this.webApplicationContext = initWebApplicationContext();
        //也是空實現,允許子類自定義
        initFrameworkServlet();
	}

所以重頭戲就在initWebApplicationContext方法上,我們可以先來看看執行後的效果:

在這裡插入圖片描述

可以看到springMVC九大組件被賦值,除此之外webApplicationContext也已被賦值。

我們再來看看源碼,看看其內部具體實現:FrameworkServlet.initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
    //根容器查找
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        //在構建時注入了DispatcherServlet並且webApplicationContext已經存在->直接使用
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                //如果context還沒有refresh-->進行設置父級context以及application context的id等等操作
                if (cwac.getParent() == null) {
                    //在沒有顯式父級的情況下注入了context實例->將根應用程序上下文設置為父級
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
		//在構造時未注入任何上下文實例-->從ServletContext中查詢
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // ServletContext中沒有-->就創建一個被本地的
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        //如果context不支持refresh或者在初始化的時候已經refresh-->就手動觸發onfresh
        onRefresh(wac);
    }
    //把當前建立的上下文存入ServletContext中,使用的屬性名和當前Servlet名相關
    if (this.publishContext) {
        // 將上下文發佈為servlet上下文屬性
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

根容器查找的方法

WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContextUtils.getWebApplicationContext

//SpringMVC支持Spring容器與Web容易同時存在,並且Spring容器視作根容器,通常由ContextLoaderListener進行加載。
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    //String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
	//根據ServletName.ROOT為鍵查找值
    Object attr = sc.getAttribute(attrName);
    if (attr == null) {
        return null;
    return (WebApplicationContext) attr;
}

Spring容器和Web容器如果同時存在,需要使用ContextLoaderListener加載Spring的配置,且它會以key為

WebApplicationContext.class.getName() + ".ROOT存到ServletContext中。

容器創建的方法

構建的時候沒有任何Context實例注入,且ServletContext中也沒有找到WebApplicationContext,此時就會創建一個local Context,這個方法允許顯式傳入父級容器作為參數。

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    //默認:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;可以在初始化參數中指定contextClass
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    //獲取ConfigurableWebApplicationContext對象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

我們可以發現:在這個過程中,Web容器的IoC容器被建立,也就是XmlWebApplicationContext,,從而在web容器中建立起整個spring應用。

configureAndRefreshWebApplicationContext(wac);

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    //省略給ConfigurableWebApplicationContext對象設置一些值...
    //每次context refresh,都會調用initPropertySources
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    //初始化webApplication容器,重啟
    wac.refresh();
}

加載配置文件信息

其實也就是refresh()這個關鍵方法,之前了解過spring容器的初始化的過程,對這一步應該相當熟悉,還是分為三步:

  • BeanDefinition的Resource的定位,我們這定位到了classpath:springmvc.xml。

在這裡插入圖片描述

  • beanDefinition的載入過程,springMVC做了一些改變,比如定義了針對mvc的命名空間解析MvcNamespaceHandler。

  • 接着是beanDefinition在IoC中的註冊,也就是把beanName:beanDefinition以鍵值對的形式存入beandefinitionMap中。

2. MVC的初始化

MVC的初始化在DispatcherServlet的initStratefies方法中執行,通過方法名,我們就可以得出結論,就是在這進行了對九大組件的初始化,其實基本上都是從IoC容器中獲取對象:

	protected void initStrategies(ApplicationContext context) {
        //文件上傳解析器
		initMultipartResolver(context);
        //區域信息解析器,與國際化相關
		initLocaleResolver(context);
        //主題解析器
		initThemeResolver(context);
        //handler映射信息解析
		initHandlerMappings(context);
        //handler的適配器
		initHandlerAdapters(context);
        //handler異常解析器
		initHandlerExceptionResolvers(context);
        //視圖名轉換器
		initRequestToViewNameTranslator(context);
        //視圖解析器
		initViewResolvers(context);
        //flashMap管理器
		initFlashMapManager(context);
	}

文件上傳解析器

private void initMultipartResolver(ApplicationContext context) {
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 默認是沒有配置multipartResolver的.
        this.multipartResolver = null;
    }
}

配置文件上傳解析器也很簡單,只需要在容器中註冊MultipartResolver即可開啟文件上傳功能。

區域信息解析器

private void initLocaleResolver(ApplicationContext context) {
    try {
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

    }
    catch (NoSuchBeanDefinitionException ex) {
        // 使用默認策略,利用反射創建對象
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
    }
}

org.springframework.web.servlet.DispatcherServlet同級目錄下的DispatcherServlet.properties文件中規定了幾大組件初始化的默認策略。

在這裡插入圖片描述

handler映射信息解析

handlerMappings存在的意義在於為HTTP請求找到對應的控制器Controller。

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		//從所有的IoC容器中導入HandlerMappings,包括其雙親上下文
		if (this.detectAllHandlerMappings) {
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
                //嘗試從容器中獲取
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
        //保證至少有一個handlerMapping
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		}
	}

接下來幾個操作都差不多,就不贅述了。

總的來說,MVC初始化的過程建立在IoC容器初始化之後,畢竟要從容器中取出這些組件對象。

3. HandlerMapping的實現原理

HandlerExecutionChain

HandlerMapping在SpringMVC扮演着相當重要的角色,我們說,它可以為HTTP請求找到 對應的Controller控制器,於是,我們來好好研究一下,這裏面到底藏着什麼玩意。

在這裡插入圖片描述

HandlerMapping是一個接口,其中包含一個getHandler方法,能夠通過該方法獲得與HTTP請求對應的handlerExecutionChain,而這個handlerExecutionChain對象中持有handler和interceptorList,以及和設置攔截器相關的方法。可以判斷是同通過這些配置的攔截器對handler對象提供的功能進行了一波增強。
在這裡插入圖片描述

RequestMappingHandlerMapping

我們以其中一個HandlerMapping作為例子解析一下,我們關注一下:

protected void initHandlerMethods() {
	//獲取所有上下文中的beanName
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                          BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                          obtainApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            //得到對應beanName的Class
            beanType = obtainApplicationContext().getType(beanName);
            //判斷是否為控制器類
            if (beanType != null && isHandler(beanType)) {
                //對控制器中的方法進行處理
                detectHandlerMethods(beanName);
            }
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

isHandler方法:判斷該類是否存在@Controller註解或者@RequestMapping註解

	@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

detectHandlerMethods方法:

protected void detectHandlerMethods(final Object handler) {
    //獲取到控制器的類型
    Class<?> handlerType = (handler instanceof String ?
                            obtainApplicationContext().getType((String) handler) : handler.getClass());
    if (handlerType != null) {
        //對類型再次進行處理,主要是針對cglib
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        //遍歷方法,對註解中的信息進行處理,得到RequestMappingInfo對象,得到methods數組
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                                                                  (MethodIntrospector.MetadataLookup<T>) method -> {
      return getMappingForMethod(method, userType);
         });
        //遍歷methods[Method,{path}]
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            //對方法的可訪問性進行校驗,如private,static,SpringProxy
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            //獲取最終請求路徑
            T mapping = entry.getValue();
            //註冊
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }
}

mapping對象的屬性:

在這裡插入圖片描述

methods對象中存儲的元素:

在這裡插入圖片描述

註冊方法在AbstractHandlerMethodMapping中實現:

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        //處理方法的對象
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        //判斷映射的唯一性
        assertUniqueMethodMapping(handlerMethod, mapping);
		//將mapping信息和控制器方法對應
        this.mappingLookup.put(mapping, handlerMethod);
		//將path與處理器映射(一個方法可能可以處理多個url)
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
		//控制器名的大寫英文縮寫#方法名
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
		//跨域請求相關配置
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
		//將所有配置統一註冊到registry中
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

至此,所有的Controller,以及其中標註了@RequestMapping註解的方法,都被一一解析,註冊進HashMap中,於是,對應請求路徑與處理方法就一一匹配,此時HandlerMapping也初始化完成。

三、請求響應處理

1. 請求分發

我們需要明確的一個點是,請求過來的時候,最先執行的地方在哪,是Servlet的service方法,我們只需要看看該方法在子類中的一個實現即可:

FrameworkServlet重寫的service方法:

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//獲取請求方法
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        //攔截PATCH請求
		if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

其實最後都是調用了processRequest方法,該方法中又調用了真正的doService()方法,其中細節先不探討,我們直奔,看看DispatcherServlet的這個doService幹了哪些事情(DispatcherServlet這個類確實是核心中的核心,既建立了IoC容器,又負責請求分發):

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//忽略一大串前期準備,使其能夠處理view 對象
    //接着進入真正的分發
	doDispatch(request, response);
	}

doService:

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 {
            //如果是文件上傳請求,對request進行包裝,如果不是就原樣返回
            processedRequest = checkMultipart(request);
            //文件上傳請求標識符
            multipartRequestParsed = (processedRequest != request);

            //為當前的request請求尋找合適的handler
            mappedHandler = getHandler(processedRequest);
            //如果沒有handler可以處理該請求,就跳轉到錯誤頁面
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            //為當前的request請求尋找合適的adapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                //判斷是否支持getLastModified,如果不支持,返回-1
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            //執行註冊攔截器的preHandle方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 真正處理請求的方法
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
			//如果mv!=null&&mv對象沒有View,則為mv對象設置一個默認的ViewName
            applyDefaultViewName(processedRequest, mv);
            //執行註冊攔截器的applyPostHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        //進行視圖解析和渲染
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
}

需要注意的是,mappedHandler和HandlerAdapter都是從對應的集合中遍歷查找,一旦找到可以執行的目標,就會停止查找,我們也可以人為定義優先級,決定他們之間的次序。

2. 請求處理

RequestMappingHandlerAdapter的handleInternal方法,含有真正處理請求的邏輯。

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    //定義返回值變量
    ModelAndView mav;
    //對請求進行檢查 supportedMethods和requireSession
    checkRequest(request);

    // 看看synchronizeOnSession是否開啟,默認為false
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        //Httpsession可用
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            //加鎖,所有請求串行化
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // 沒有可用的Httpsession -> 沒必要上鎖
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // 正常調用處理方法
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    //檢查響應頭是否包含Cache-Control
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    return mav;
}

RequestMappingHandlerAdapter的invokeHandlerMethod方法,真正返回mv。

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    //對HttpServletRequest進行包裝,產生ServletWebRequest處理web的request對象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        //創建WebDataBinder對象的工廠
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        //創建Model對象的工廠
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		//將handlerMethod對象進行包裝,創建ServletInvocableHandlerMethod對象
        //向invocableMethod設置相關屬性(最後是由invocableMethod對象調用invokeAndHandle方法
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		//創建ModelAndViewContainer對象,裏面存放有向域中存入數據的map
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		//省略異步處理
        //正常調用
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
		//獲取ModelAndView對象
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

ServletInvocableHandlerMethod的invokeAndHandle方法:反射調用方法,得到返回值。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
	//獲取參數,通過反射得到返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    //設置響應狀態
    setResponseStatus(webRequest);
	
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        //處理返回值
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
        }
        throw ex;
    }
}

參數解析過程

我們可以知道的是,傳遞參數時,可以傳遞Map,基本類型,POJO,ModelMap等參數,解析之後的結果又如何呢?我們以一個具體的例子舉例比較容易分析:

    @RequestMapping("/handle03/{id}")
    public String handle03(@PathVariable("id") String sid,
                           Map<String,Object> map){
        System.out.println(sid);
        map.put("msg","你好!");
        return "success";
    }
/**
 * 獲取當前請求的方法參數值。
 */
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    //獲取參數對象
    MethodParameter[] parameters = getMethodParameters();
    //創建一個同等大小的數組存儲參數值
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            //參數處理器處理參數(針對不同類型的參數有不同類型的處理參數的策略)
            args[i] = this.argumentResolvers.resolveArgument(
                parameter, mavContainer, request, this.dataBinderFactory);
            continue;
        }
        if (args[i] == null) {
            throw new IllegalStateException();
    	}
    return args;
}

resolveArgument方法:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	//獲取註解的信息
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    //包裝parameter對象
    MethodParameter nestedParameter = parameter.nestedIfOptional();
	//獲取@PathVariable指定的屬性名
    Object resolvedName = resolveStringValue(namedValueInfo.name);
    //
    if (resolvedName == null) {
        throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }
	//根據name從url中尋找並獲取參數值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    //沒有匹配
    if (arg == null) {
        //如果有default值,則根據該值查找
        if (namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }
        //如果required為false,則可以不指定name,但默認為true。
        else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
        }
        
        arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
    }
    //雖然匹配,路徑中傳入的參數如果是「 」,且有默認的name,則按照默認處理
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
        arg = resolveStringValue(namedValueInfo.defaultValue);
    }

    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);   
    }
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    return arg;
}

getNameValueInfo方法:

	private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        //從緩存中獲取
		NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
		if (namedValueInfo == null) {
            //創建一個namedValueInfo對象
			namedValueInfo = createNamedValueInfo(parameter);
            //如果沒有在註解中指定屬性名,默認為參數名
			namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            //更新緩存
			this.namedValueInfoCache.put(parameter, namedValueInfo);
		}
		return namedValueInfo;
	}

createNamedValueInfo:獲取@PathVariable註解的信息,封裝成NamedValueInfo對象

	@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
		Assert.state(ann != null, "No PathVariable annotation");
		return new PathVariableNamedValueInfo(ann);
	}

updateNamedValueInfo:

private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
    String name = info.name;
    if (info.name.isEmpty()) {
        //如果註解中沒有指定name,則為參數名
        name = parameter.getParameterName();
        if (name == null) {
            throw new IllegalArgumentException(
                "Name for argument type [" + parameter.getNestedParameterType().getName() +
                "] not available, and parameter name information not found in class file either.");
        }
    }
    String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
    return new NamedValueInfo(name, info.required, defaultValue);
}

resolveName方法:

在這裡插入圖片描述

參數解析的過程:

  • 根據方法對象,獲取參數對象數組,並創建存儲參數的數組。
  • 遍歷參數對象數組,並根據參數解析器argumentResolver解析。
  • 如果沒有參數解析器,報錯。
  • 參數解析時,先嘗試獲取註解的信息,以@PathVariable為例。
  • 根據指定的name從url中獲取參數值,如果沒有指定,則默認為自己傳入的參數名。

傳遞頁面參數

我們可能會通過Map、Model、ModelMap等向域中存入鍵值對,這部分包含在請求處理中。

我們要關注的是ModelAndViewContainer這個類,它裏面默認包含着BindingAwareModelMap。

在解析參數的時候,就已經通過MapMethodProcessor參數處理器初始化了一個BindingAwareModelMap。

在這裡插入圖片描述
在這裡插入圖片描述

當然其實這裡重點還是參數解析,至於數據為什麼封裝進map,就很簡單了,無非是反射執行方法的時候,通過put將數據存入,當然最後的數據也就存在於ModelAndViewContainer中。

返回值解析

省略尋找返回值解析器的過程,因為返回值為視圖名,所以解析器為:ViewNameMethodReturnValueHandler。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    if (returnValue instanceof CharSequence) {
        //獲取視圖名
        String viewName = returnValue.toString();
        //向mavContainer中設置
        mavContainer.setViewName(viewName);
        //是否是isRedirectViewName
        if (isRedirectViewName(viewName)) {
            mavContainer.setRedirectModelScenario(true);
        }
    }
    else if (returnValue != null){
        // should not happen
        throw new UnsupportedOperationException("Unexpected return type: " +
                                                returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
    }
}

isRedirectViewName方法

	protected boolean isRedirectViewName(String viewName) {
        //是否符合自定義的redirectPatterns,或者滿足redirect:開頭的名字
		return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
	}

最後通過getModelAndView獲取mv對象,我們來詳細解析一下:

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
	//Promote model attributes listed as @SessionAttributes to the session
    modelFactory.updateModel(webRequest, mavContainer);
    //如果請求已經處理完成
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    //從mavContainer中獲取我們存入的數據map
    ModelMap model = mavContainer.getModel();
    //通過視圖名、modelmap、和status創建一個ModelAndView對象
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

最後返回的都是ModelAndView對象,包含了邏輯名和模型對象的視圖。

返回值解析的過程相對比較簡單:

  • 根據返回的參數,獲取對應的返回值解析器。

  • 獲取視圖名,如果是需要redirect,則mavContainer.setRedirectModelScenario(true);

  • 其他情況下,直接給mvcContainer中的ViewName視圖名屬性設置上即可。

  • 最後將mvcContainer的model、status、viewName取出,創建mv對象返回。

【總結】

參數解析、返回值解析兩個過程都包含大量的解決策略,其中尋找合適的解析器的過程都是先遍歷初始化的解析器表,然後判斷是否需要異步處理,判斷是否可以處理返回值類型,如果可以的話,就使用該解析器進行解析,如果不行,就一直向下遍歷,直到表中沒有解析器為止。

3. 視圖解析

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 保證渲染一次,cleared作為標記
    if (mv != null && !mv.wasCleared()) {
        //渲染過程!!!
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
}

DispatcherServlet的render方法

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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    //獲取視圖名
    String viewName = mv.getViewName();
    if (viewName != null) {
        //通過視圖解析器viewResolvers對視圖名進行處理,創建view對象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    }
    else {
        view = mv.getView();
    }
    if (mv.getStatus() != null) {
        response.setStatus(mv.getStatus().value());
    }
    view.render(mv.getModelInternal(), request, response);   
}

獲取視圖解析器,解析視圖名:

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {

    //這裡我們註冊的是InternalResourceViewResolver
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

UrlBasedViewResolver的createView方法:

	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		//如果解析器不能處理所給的view,就返回null,讓下一個解析器看看能否執行
		if (!canHandle(viewName, locale)) {
			return null;
		}
		// Check for special "redirect:" prefix.
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			//判斷是否需要重定向
		}
		// Check for special "forward:" prefix.
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			//判斷是否需要轉發
		}
		//調用父類的loadView方法
		return super.createView(viewName, locale);
	}

最後返回的視圖對象:

在這裡插入圖片描述

視圖解析器 viewResolver –實例化 –> view(無狀態的,不會有線程安全問題)

AbstractView的render方法

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
	
    //獲取合併後的map,有我們存入域中的map,還有PathVariable對應的鍵值等
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    //根據給定的model渲染內部資源,如將model設置為request的屬性
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

InternalResourceView的renderMergedOutputModel

@Override
protected void renderMergedOutputModel(
    Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    //將model中的值設置到request域中
    exposeModelAsRequestAttributes(model, request);

    // 如果有的話,給request設置helpers
    exposeHelpers(request);

    // 將目標地址設置到request中
    String dispatcherPath = prepareForRendering(request, response);

    // 獲取目標資源(通常是JSP)的RequestDispatcher。
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

    // 如果已經包含或響應已經提交,則執行包含,否則轉發。
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        rd.include(request, response);
    }
    else {
        // Note: 轉發的資源應該確定內容類型本身。
        rd.forward(request, response);
    }
}

exposeModelAsRequestAttributes

protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {
	//遍歷model
    model.forEach((modelName, modelValue) -> {
        if (modelValue != null) {
            //向request中設置值
            request.setAttribute(modelName, modelValue);
        }
        else {
            //value為null的話,移除該name
            request.removeAttribute(modelName);
        }
    });
}

視圖解析器

視圖解析器(實現ViewResolver接口):將邏輯視圖解析為具體的視圖對象。

在這裡插入圖片描述
每個視圖解析器都實現了Ordered接口,並開放order屬性,order越小優先級越高。

按照視圖解析器的優先順序對邏輯視圖名進行解析,直到解析成功並返回視圖對象,否則拋出異常。

視圖

視圖(實現View接口):渲染模型數據,將模型數據以某種形式展現給用戶。

最終採取的視圖對象對模型數據進行渲染render,處理器並不關心,處理器關心生產模型的數據,實現解耦。