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="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://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,mv);
        }
        //进行视图解析和渲染
        processDispatchResult(processedRequest,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,handlerMethod);
            }
        }
        else {
            // 没有可用的Httpsession -> 没必要上锁
            mav = invokeHandlerMethod(request,handlerMethod);
        }
    }
    else {
        // 正常调用处理方法
        mav = invokeHandlerMethod(request,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,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,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),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,request,this.dataBinderFactory);
            continue;
        }
        if (args[i] == null) {
            throw new IllegalStateException();
    	}
    return args;
}

resolveArgument方法

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter,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,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,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,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,@Nullable HandlerExecutionChain mappedHandler,@Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 保证渲染一次,cleared作为标记
    if (mv != null && !mv.wasCleared()) {
        //渲染过程!!!
        render(mv,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(),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,HttpServletResponse response) throws Exception {
	
    //获取合并后的map,有我们存入域中的map,还有PathVariable对应的键值等
    Map<String,Object> mergedModel = createMergedOutputModel(model,response);
    prepareResponse(request,response);
    //根据给定的model渲染内部资源,如将model设置为request的属性
    renderMergedOutputModel(mergedModel,getRequestToExpose(request),response);
}

InternalResourceView的renderMergedOutputModel

@Override
protected void renderMergedOutputModel(
    Map<String,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,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,处理器并不关心,处理器关心生产模型的数据,实现解耦。

相关文章

SpringMVC学习记录 注意:以下内容是学习 北京动力节点 的SpringMVC视频后所记录的笔记、源码以及个人的...
SpringMVC拦截器
SpringMVC学习记录 注意:以下内容是学习 北京动力节点 的SpringMVC视频后所记录的笔记、源码以及个人的...
SpringMVC流程及源码分析 前言 ​&#x9;学了一遍SpringMVC以后,想着做一个总结,复习一下。复习写下...
1、添加命名空间和xsd约束 2、在 application.xml文件中开启注解 3、配置扫描任务扫描包 4、demo
SpringMVC 概述 Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架,是目前最主流的 MVC 框架之...