spring mvc 详解

很久以前研究过spring mvc,也是看着很多网上的文章,边摸索边学习。如今突然觉得仅仅是看代码学习其实效果不好。经常容易忘记。

学习的最好方式还是自动动手尝试,或者写总结。自从自己弄了个博客后,越来越喜欢写一些文字,算是给自己一个交代。后悔没有早些开始写博客。

好吧废话不多说,仅仅是记录下个人对spring mvc 中 DispatcherServlet的实现逻辑详解。

doService

DispatcherServlet extends FrameworkServlet extends HttpServlet,整个调用链如下图
image.png
首先进入的方法是doService,doService接口是在FrameworkServlet中定义的。DispatcherServlet实现,源码如下

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		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 {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

doService中的代码没啥好说的, 基本上都是在往request 对象中 setAttribute。方便后续方法获取使用对象。mvc整体的架构还是在doDispatch中体现的。

doDispatch

doDispatch体现了mvc的核心架构,在doDispatch中分别包含了如下几点:

  • 获取HandlerMapping
  • 根据HandlerMapping获取适配的HandlerAdapter
  • HandlerAdapter执行controller中的handlerMethod,并返回ModelAndView
  • ViewResolver负责解析渲染ModelAndView

DispatcherServlet 核心的架构就是如上过程,当然还有很多细节,比如如何返回json格式的数据,文件上传等等。接下来我们继续深入代码。先看一下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 {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
		     		// 显示检查了下是否是文件流请求,如果是文件流,checkMultipart内部会有额外的处理逻辑
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				//A 获取handlerMapping,通过handlerMapping构建HandlerExecutionChain
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// B 根据handlerMapping获取对应的HandlerAdapter 
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//C 执行HandlerExecutionChain中的前置拦截	
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//D 真正去执行controller代码方法的调用入口在这里,当然内部不仅仅是执行controller中的method
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				applyDefaultViewName(processedRequest, mv);
				//E 执行HandlerExecutionChain中的后置处理
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			// 
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

以上代码比较重要的基础标识了A、B、C、D,我们来细说下这几处代码。

A 获取handlerMapping,通过handlerMapping构建HandlerExecutionChain

handlerMapping,在spring boot 之前,我们配置DispatcherServlet时都需要配置一个handlerMapping,spring boot就很智能了,不需要我们配了,直接通过注解就实现了默认配置。我们常用的handlerMapping是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。

handlerMapping是做什么的呢,handlerMapping的顶层接口是HandlerMapping,在一个web工程中存在很多不同种类的资源,如静态资源、servlet、通过注解实现的controller、通过实现顶层controller接口实现的controller。每一种不同的资源都对应一个HandlerMapping,通过HandlerMapping在管理这些资源。如下图
image.png

在HandlerMapping中管理着所有的mapping映射资源,如下图
image.png
上图中我们看到HandlerMapping中使用一个Map来存储mapping映射资源的,想象一下一个request请求如何将请求转发到controller中的method。靠的就是HandlerMapping,调用链如下:

  • HandlerMapping.getHandler
  • AbstractHandlerMapping.getHandlerInternal
  • AbstractHandlerMapping.getHandlerExecutionChain

DispatcherServlet.doDispatch中A段代码进入到getHandler(),通过上面的介绍HandlerMapping是多个的,所以循环HandlerMapping集合,调用handlerMapping.getHandler()方法,如果返回不为空,那么就直接return了,代码如下

	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping hm : this.handlerMappings) {
				if (logger.isTraceEnabled()) {
					logger.trace(
							"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
				}
				// 执行getHandler方法,返回的是HandlerExecutionChain,下面会介绍内容逻辑
				HandlerExecutionChain handler = hm.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

HandlerExecutionChain 包含了HandlerInterceptor(拦截器),HandlerMethod(需要执行的controller中的method)。到这里是不是就很清晰了。接下来我们来看看mvc是如何构建HandlerExecutionChain 对象的。
从 AbstractHandlerMapping.getHandlerInternal开始说起,代码如下

	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// 获取HandlerMethod
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		// 构建HandlerExecutionChain 
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}
  • getHandlerInternal核心代码在AbstractHandlerMethodMapping,逻辑是根据request的URI获取HandlerMapping中的HandlerMethod
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		...
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		...

	}
  • getHandlerExecutionChain核心代码就是加入拦截器,HandlerInterceptor相信大家应该不陌生,不知道有没有人用这个做简单的日志记录的。
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			// MappedInterceptor 可以匹配URL进行拦截
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				// 其他拦截器,所有HandlerMehtod都会拦截
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

到了目前为止,HandlerMapping和HandlerExecutionChain 应该就很清楚了,HandlerMapping找到request请求唯一的HandlerMethod,并且封装成HandlerExecutionChain,而HandlerExecutionChain中就包含了HandlerMethod和HandlerInterceptor。接下来就是HandlerAdapter也就是DispatcherServlet.doDispatch中B段代码

获取HandlerAdapter

获取HandlerAdapter逻辑很简单,截两张图,handler是HandlerExecutionChain.getHandler()得到的对象通过参数传递过来的,我们知道此时的handler对象就是HandlerMethod。所以得到的HandlerAdapter 就是RequestMappingHandlerAdapter

image.png

image.png

但是我们依然要知道为什么需要HandlerAdapter,我们上面说过HandlerMapping是有很多类型的,比如servlet、静态资源、注解controller、接口controller。现象一下,每一种类型其实处理的方式都不一样,比如参数注入、结果解析等等每一种HandlerMapping都可能不一样,比如Servlet和注解Controller在参数注入和结果解析明显就有很大的不同之处, 想想我们的设计模式,单一原则等等,很自然就需要多一层HandlerAdapter。而HandlerAdapter就是真正要去调用Method的处理类。

获取到HandlerAdapter就好办了,调用下前置处理(代码片段C),接下来的事情就全部交给HandlerAdapter吧(代码片段D)

调用HandlerAdapter.handler接口

HandlerAdapter.handler调用链如下

  • AbstractHandlerMethodAdapter.handler

  • RequestMappingHandlerAdapter.handleInternal

  • RequestMappingHandlerAdapter.invokeHandlerMethod

  • ServletInvocableHandlerMethod.invokeAndHandle
    核心代码在RequestMappingHandlerAdapter.invokeHandlerMethod和ServletInvocableHandlerMethod.invokeAndHandle

  • RequestMappingHandlerAdapter.invokeHandlerMethod代码如下

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			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 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);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				if (logger.isDebugEnabled()) {
					logger.debug("Found concurrent result value [" + result + "]");
				}
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

上面代码,看似复杂其实就是为了构建ServletInvocableHandlerMethod,ServletInvocableHandlerMethod几个重要的对象是WebDataBinderFactory和ModelFactory 看这些命名基本上就知道是为了参数绑定注入、Model构建(最后返回我们的ModuleAndView中的Model)。

  • ServletInvocableHandlerMethod.invokeAndHandle代码如下
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 参数解析绑定,并利用反射调用Method
		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 {
			// 处理返回结果(比如返回json)
			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;
		}
	}

上面代码主要目的就是参数解析绑定然后反射调用函数,函数调用返回值的处理。invokeForRequest核心调用链

  • invokeForRequest
  • getMethodArgumentValues
    getMethodArgumentValues中最核心的代码其实argumentResolvers,
	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)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				catch (Exception ex) {
					if (logger.isDebugEnabled()) {
						logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
					}
					throw ex;
				}
			}
			if (args[i] == null) {
				throw new IllegalStateException("Could not resolve method parameter at index " +
						parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
						": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
			}
		}
		return args;
	}

一直玩下追踪,HandlerMethodArgumentResolverComposite

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //获取方法参数解析器
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        }
        //解析参数
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }


    //获取方法参数解析器的方法
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                            parameter.getGenericParameterType() + "]");
                }
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

HandlerMethodArgumentResolver是参数解析器,只有两个方法,但是有很多的实现方案
image.png
实现方案
image.png

  • RequestParamMethodArgumentResolver
    支持两种场景。一种是含@RequestParam注解的参数,另一种就是简单类型,如Integer、String、Date、URI, URL,Locale等

  • RequestParamMapMethodArgumentResolver
    用来获取所有的header信息。参数类型可以是普通的Map类型,也可以是MultiValueMap或者进一步的Http请求参数,他们与普通Map类型的区别是他们对value值后者们是以List形式存放,前者是以String形式存放。

  • RequestHeaderMethodArgumentResolver
    主要用来处理含有@RequestHeader注解的参数,但同时该参数又不是Map类型。

  • RequestHeaderMapMethodArgumentResolver
    用来获取所有的header信息。参数类型可以是普通的Map类型,也可以是MultiValueMap或者进一步的HttpHeaders,他们与普通Map类型的区别是他们对value值后者们是以List形式存放,前者是以String形式存放。

  • ServletCookieValueMethodArgumentResolver
    主要处理处理器方法参数中带有@CookieValue的解析工作。它的验证代码在AbstractCookieValueMethodArgumentResolver中

  • PathVariableMethodArgumentResolver
    主要针对含有@PathVariable的参数。必须要指定@PathVariable的值,即这个ArgumentResolver只能获取一个uri变量,要想获取多个则要使用PathVariableMapMethodArgumentResolver。

  • PathVariableMapMethodArgumentResolver
    它要求必须含有@PathVariable注解,并且必须是Map类型,并且@PathVariable注解的value没有值。

解析并绑定参数后,就可以利用java反射原理实现函数调用了。函数执行完成后,处理返回值,回到ServletInvocableHandlerMethod.invokeAndHandle代码,继续往下处理返回值的代码在类HandlerMethodReturnValueHandlerComposite中

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

        //获取返回值处理器
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        }

        //处理返回值
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

    //获取返回值处理器
    private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    } 

因为现在大部分后端处理都是直接返回的json数据,所以这里就直接输出json数据,并将ModelAndViewContainer对象中的requestHandler属性置为true。这样在RequestMappingHandlerAdapter上层代码中就不会在返回ModuleAndView对象了。

public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        //这里设置了请求已被处理,在上层方法中将不会返回ModelAndView对象了。
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        // Try even with null return value. ResponseBodyAdvice could get involved.
        //将找到返回值转换
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

        // 以下代码在RequestMappingHandlerAdapter中
	@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

		modelFactory.updateModel(webRequest, mavContainer);
 		// ResponseBody注解,ModelAndView 为空
		if (mavContainer.isRequestHandled()) {
			return null;
		}
		ModelMap model = mavContainer.getModel();
		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;
	}

好了,本次先写ViewandResolve了。时间有限,感觉篇幅太长,很多东西也无法细讲

# java   spring    sprimg mvc  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×