接上一节的分析,接下来应该调用Controller中的具体方法了,但是在调用之前,还要有参数解析、InitBinder方法初始化、InitBinder方法调用等工作,接下来逐步分析。
public void invokeAndHandle(
ServletWebRequest webRequest,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1.调用Controller中的具体方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 2.设置返回状态码
setResponseStatus(webRequest);
// 3.当前请求无返回值或者返回值中包含错误,则将请求完成标识设置为true并返回
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
// 4.当前请求有返回值且无错误信息,则将请求完成标识设置为false,并继续处理当前请求
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 选取合适的HandlerMethodReturnValueHandler,并处理返回值
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
最重要的就是第一步invokeForRequest方法:
public Object invokeForRequest(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取并解析请求参数
/**
* 注意这里不一定都是解析@RequestMapping方法的参数,
* 也有可能会解析@InitBinder方法的参数
*
* 所以下面的doInvoke方法也并不一定调用具体的@RequestMapping方法,
* 也有可能调用@InitBinder方法进行参数的解析绑定
*/
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 调用方法
return doInvoke(args);
}
该方法看起来很简单,只有两个函数调用,但是其背后的逻辑还是相当复杂的。另外如果有同学设置了@InitBinder注解,那么这里的调用可能会有一些绕,因为这里不仅仅调用的是@RequestMapping方法,前面介绍过@InitBinder注解的方法会先于@RequestMapping调用,那么其调用的时机就是在解析参数的时候,也就是这里。接下来的处理分为两步,一是参数处理,二是方法调用。
private Object[] getMethodArgumentValues(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1.获取方法参数列表,并创建与参数个数相同的Object数组,用来保存解析的参数值
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
// 2.解析参数
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 这里当解析@InitBinder参数时会指定providedArgs参数,无需纠结...
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 参数解析器是否支持对该参数的解析
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
// 调用参数解析器的解析方法
/**
* SpringMVC的参数解析器顶级接口为HandlerMethodArgumentResolver
* 该接口只提供了两个方法:supportsParameter和resolveArgument
*
* 我们也可以自定义参数解析器,只需实现这两个方法即可
*/
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
// Leave stack trace for later, e.g. AbstractHandlerExceptionResolver
if (logger.isDebugEnabled()) {
String message = ex.getMessage();
if (message != null && !message.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, message));
}
}
throw ex;
}
}
// 如未能正常解析参数且未抛出异常,则说明当前参数没有合适的参数解析器,抛出 'No suitable resolver' 异常
if (args[i] == null) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
}
return args;
}
从代码中可以看到,具体的参数解析工作委托给了HandlerMethodArgumentResolver,HandlerMethodArgumentResolver是一个接口,其中只有两个方法:
public interface HandlerMethodArgumentResolver {
/**
* 此解析器是否支持给定的方法参数。
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 将方法参数解析为给定请求的参数值。
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
那么看到这里,大家一定也能想到,既然这个类是一个接口,那么必然有多个实现,接下来就应该查找具体的参数解析器、并调用解析器的resolveArgument方法对参数进行解析:
public Object resolveArgument(
MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable 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);
}
上述代码就是干这些事情的,接下来以AbstractNamedValueMethodArgumentResolver为例,看一下参数的具体解析过程:
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 1.NamedValueInfo对象包含了name,required,defaultValue三个信息
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// 获取MethodParameter对象,该对象封装了方法参数的规范
MethodParameter nestedParameter = parameter.nestedIfOptional();
// 2.解析参数名,包括占位符和表达式等
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 3.将给定的参数类型和值名称解析为参数值。
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
// 如果未能正常解析
/**
* 如
* 方法参数 : @RequestParam(name = "name") String name
* 请求路径参数后缀 : sayHello?1212
*
* 未指定参数名称,则无法正常解析,接下来要判断NamedValueInfo属性值,并作出后续处理
*/
if (arg == null) {
// 如果默认值不为空,则
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 指定了required属性且该参数不是为非不必须,则调动handleMissingValue方法处理缺失值,该方法一般会抛出异常
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 最后处理将该参数值处理为null即可
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
// 4.创建WebDataBinder实例
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 5.尝试转换参数
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
前面对于参数的各种情况的处理,都比较简单,大家可以多写一些实例,多测试即可;接下来要看WebDataBinder实例的创建和convertIfNecessary函数的调用过程。
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
// 1.创建WebDataBinder实例
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
// 2.初始化initBinder
initBinder(dataBinder, webRequest);
return dataBinder;
}
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, binder)) {
// 注意这里有invokeForRequest方法...
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
}
}
}
}
这里我们可以看到在initBinder的过程中会有对invokeForRequest方法的调用,从这里我们也可以看到@InitBinder方法是优先于@RequestMapping方法调用的。
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam)
throws TypeMismatchException {
return doConvert(value, requiredType, methodParam, null);
}
private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
if (field != null) {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
}
else {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
}
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
// 1、判断有无自定义属性编辑器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// No custom editor but custom ConversionService specified?
// 2、判断有无自定义ConversionService
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// Value not of required type?
// ClassUtils.isAssignableValue(requiredType, convertedValue)-->判断requiredType和convertedValue的class,是否相同,
// 相同返回->true;否则返回->false
// 3、 如果有自定义属性编辑器或者通过解析出来的值类型与真实的值类型的class不同
// 例如 ,我们需要将value转换成int时
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
// 4、执行转换
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate.
if (convertedValue != null) {
// Object类型
if (Object.class == requiredType) {
return (T) convertedValue;
}
// 数组类型
else if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
// 集合类型
else if (convertedValue instanceof Collection) {
// Convert elements to target type, if determined.
convertedValue = convertToTypedCollection((Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
// map类型
else if (convertedValue instanceof Map) {
// Convert keys and values to respective target type, if determined.
convertedValue = convertToTypedMap((Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
// 注意:这里是新开启的if,不接上面的else if
// 如果经过转换过的值是数组类型,且其长度只有1,那么只取其第0个作为最终转换值
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
// 如果类型是String,并且是java的基本数据类型或者包装类型
// 包括 boolean, byte, char, short, int, long, float, double
// 和 Boolean, Byte, Character, Short, Integer, Long, Float, Double
// 那么直接调用toString()方法返回即可,注意convertedValue是Object类型,不是基本或包装类型,所以是可以调用toString()方法的
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
// We can stringify any primitive value...
return (T) convertedValue.toString();
}
// 如果转换值是String类的实例,但是我们又不能转换为解析出来的requiredType的实例
// 例如枚举类型值的注入
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
try {
Constructor<T> strCtor = requiredType.getConstructor(String.class);
return BeanUtils.instantiateClass(strCtor, convertedValue);
}
// 删除logger信息
catch (NoSuchMethodException ex) {
// proceed with field lookup
if (logger.isTraceEnabled()) {
logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
}
}
}
String trimmedValue = ((String) convertedValue).trim();
if (requiredType.isEnum() && "".equals(trimmedValue)) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion = true;
}
// 数值类型
else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
convertedValue = NumberUtils.convertNumberToTargetClass((Number) convertedValue, (Class<Number>) requiredType);
standardConversion = true;
}
}
else {
// convertedValue == null
if (requiredType == Optional.class) {
convertedValue = Optional.empty();
}
}
// 5、 判定requiredType是否可从convertedValue转换而来,并尝试使用conversionService转换,及处理转换异常
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (conversionAttemptEx != null) {
// Original exception from former ConversionService call above...
throw conversionAttemptEx;
}
else if (conversionService != null && typeDescriptor != null) {
// ConversionService not tried before, probably custom editor found
// but editor couldn't produce the required type...
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
// 到此为止,可以确定类型不匹配,无法转换,抛出IllegalArgumentException/IllegalStateException
StringBuilder msg = new StringBuilder();
msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
if (propertyName != null) {
msg.append(" for property '").append(propertyName).append("'");
}
if (editor != null) {
msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
"] returned inappropriate value of type '").append(
ClassUtils.getDescriptiveType(convertedValue)).append("'");
throw new IllegalArgumentException(msg.toString());
}
else {
msg.append(": no matching editors or conversion strategy found");
throw new IllegalStateException(msg.toString());
}
}
}
if (conversionAttemptEx != null) {
if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
throw conversionAttemptEx;
}
}
// 6、返回转换值
return (T) convertedValue;
}
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
该方法比较简单,大家自行跟踪调试即可。到这里
@InitBinder创建、初始化、参数的解析、转换、调用以及@RequestMapping方法的调用就完成了。
继续上面的分析,接下来就应该设置状态码了:
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
// 获取HttpStatus
HttpStatus status = getResponseStatus();
// 未发现HttpStatus直接返回
if (status == null) {
return;
}
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
/**
* 注意 注意 注意:这里是 sendError , 不是 setError
* 使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。
* 服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,
* 这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。
*
* 如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。
*/
response.sendError(status.value(), reason);
}
else {
/**
* 设置响应的状态码。
* 这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。
* 如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。
* 使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。
*/
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
如果@RequestMapping设置了@ResponseStatus注解,这里则要根据注解设置放回状态码。如果getResponseStatusReason方法返回了错误信息,则直接通过sendError方法返回给前端。否则将状态码信息设置到response里即可。
在后续处理根据@RequestMapping方法返回值、相应信息等判断,是否将当前请求设置为已经完成。例如当前请求无需返回视图、或者当前请求的放回状态码包含了错误信息,则无需继续后续处理。
假设当前是有视图或者返回值,接下来应该选取合适的HandlerMethodReturnValueHandler并处理返回值,先来看一下HandlerMethodReturnValueHandler的定义:
public interface HandlerMethodReturnValueHandler {
/**
* 判断当前策略(Handler)是否支持MethodParameter(方法返回类型)
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* 处理返回值,为模型添加属性、视图等想关内容
*/
void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception;
}
对于其实现者,只需实现这两个方法即可。这里以ModelAndViewMethodReturnValueHandler为例看其具体的处理过程:
public void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 选取合适的HandlerMethodReturnValueHandler,如果没有找到则抛出异常
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 当前请求返回值为null,无需处理,并且要将当前请求标记已处理
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
// 处理引用视图
ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = mav.getViewName();
mavContainer.setViewName(viewName);
if (viewName != null && isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
// 处理普通视图(即我们已经制定了具体的View视图,而无需通过视图解析器再次解析)
else {
View view = mav.getView();
mavContainer.setView(view);
if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
// 处理属性
mavContainer.setStatus(mav.getStatus());
mavContainer.addAllAttributes(mav.getModel());
}
这里又涉及到两个概念,即引用视图以及普通视图(姑且命名为普通视图)。引用视图如没有指定具体的View类型,而只是通过ModelAndView对象的setViewName设置了返回视图的名称,则该视图还需要再次被解析;普通视图正好相反。
到这里invokeAndHandle方法的调用就完成了,接下来是getModelAndViewd对返回的模型做了进一步的处理。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// 1.更新模型
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
// 2.获取ModelMap并创建ModelAndView
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
// 3.处理引用类型视图和转发类型视图
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;
}