SpringMVC源码(九)- ModelAndView(View为String类型)返回值的解析过程

目录

一、ModelAndView返回值的解析过程

1、适配返回值解析器并解析(handleReturnValue)- ModelAndViewMethodReturnValueHandler

2 、视图和数据处理(getModelAndView)

3、结果处理(processDispatchResult)

二、View为String类型的解析过程

1、适配返回值解析器并解析(handleReturnValue)- ViewNameMethodReturnValueHandler

2 、视图和数据处理(getModelAndView)

3、结果处理(processDispatchResult)


    为了方便,当前Spring版本为5.2.0,Spring MVC使用的是Spring boot上进行测试。当前的视图使用Thymeleaf。开始分析前先看看ModelAndView的结构:

public class ModelAndView {

	// Object类型的视图,其实要不就是String类型,要不就是View类型
	@Nullable
	private Object view;

	// ModelMap其实就是一个LinkedHashMap(需要注意是有序的)
	@Nullable
	private ModelMap model;

	// Http状态,HttpServletResponse的状态
	@Nullable
	private HttpStatus status;

	/** Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */
	private boolean cleared = false;
}

 

一、ModelAndView返回值的解析过程

1、适配返回值解析器并解析(handleReturnValue)- ModelAndViewMethodReturnValueHandler

    继续在调用完Controller方法后,对结果进行适配:

this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

    返回值类型为标准的结果ModelAndView,适配结果为ModelAndViewMethodReturnValueHandler类型。适配方法如下:

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}

    直接用ModelAndView类型进行判断,其实还是比较直接。适配完直接,调用方法进行解析:

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

    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);
        }
    }
    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());
}

    直接将结果转换为ModelAndView,设置属性值到ModelAndViewContainer 中,getModelAndView中进行解析

    1、视图可以是String或者View类型,所以分析进行处理。可能还需要处理重定向的属性

    2、设置Http状态值

    3、返回数据(设置MVC中的Model数据)

 

2 、视图和数据处理(getModelAndView)

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                     ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    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;
}

    上一步将解析的数据都设置到ModelAndViewContainer 中了,所以,当前只要对其进行处理即可。还是那些数据,只是重新new了一个ModelAndView,设置属性。 如果是重定向则将Spring MVC的九大件之一的FlashMap中的重定向属性进行了设置。

 

3、结果处理(processDispatchResult)

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

    主要是调用了render方法进行渲染视图和模型,后续完成了拦截器(HandlerInteceptor)的后置处理方法回调。

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) {
        // We need to resolve the view name.
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("省略");
        }
    } else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("省略");
        }
    }
    // 省略try catch代码
    if (mv.getStatus() != null) {
        response.setStatus(mv.getStatus().value());
    }
    view.render(mv.getModelInternal(), request, response);
}

    1、获取local信息(国际化)

    2、判断视图类型(如果是String类型需要进行解析具体的View)

    3、View的render方法调用(当前为ThymeleafView的render,其他视图类型会有相应的渲染器,这里就不分析了)

 

其中,getViewName其实就是判断视图是String类型还是View类型。

@Nullable
public String getViewName() {
    return (this.view instanceof String ? (String) this.view : null);
}

解析String类型为View,又是挨个进行适配(但是ThymeleafView不是通过ThymeleafViewResolver适配出来的,而是ContentNegotiatingViewResolver),过程如下:

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

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

 

二、View为String类型的解析过程

1、适配返回值解析器并解析(handleReturnValue)- ViewNameMethodReturnValueHandler

    返回值类型为String,适配结果为ViewNameMethodReturnValueHandler类型。适配方法如下:

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    Class<?> paramType = returnType.getParameterType();
    return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}

    适配的类型可以是void,或者String的父类CharSequence类型。适配完直接,调用方法进行解析:

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

    if (returnValue instanceof CharSequence) {
        String viewName = returnValue.toString();
        mavContainer.setViewName(viewName);
        if (isRedirectViewName(viewName)) {
            mavContainer.setRedirectModelScenario(true);
        }
    } else if (returnValue != null) {
        throw new UnsupportedOperationException("省略");
    }
}

    上面判断了void类型,现在只判断为String类型的,否则就直接抛异常。处理更简单,只管String类型的视图添加进去,没有ModelMap就完了。

2 、视图和数据处理(getModelAndView)

    getModelAndView时,new了一个ModelAndView,只是视图为String类型,ModelMap为null。

3、结果处理(processDispatchResult)

    后面就与上面ModelAndView处理相同。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于配置文件的Spring MVC应用中,实现登录功能需要编写以下源代码: 1. LoginController.java: @Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginForm() { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) { if (username.equals("admin") && password.equals("123456")) { model.addAttribute("username", username); return "success"; } else { model.addAttribute("error", "用户名或密码错误"); return "login"; } } } 2. login.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录页面</title> </head> <body> <h1>登录</h1> <form method="POST" action="/login"> <label for="username">用户名:</label> <input type="text" id="username" name="username"> <label for="password">密码:</label> <input type="password" id="password" name="password"> <input type="submit" value="登录"> </form> <c:if test="${not empty error}"> <p>${error}</p> </c:if> </body> </html> 3. success.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录成功</title> </head> <body> <h1>登录成功</h1> <p>欢迎,${username}!</p> </body> </html> 以上代码实现了一个简单的登录功能。用户输入用户名和密码后,点击登录按钮提交表单,后台LoginController中的login方法根据用户名和密码进行验证。如果验证通过,将用户名存入Model中,并返回success视图,显示登录成功页面,页面中会显示欢迎用户名。如果验证不通过,将错误信息存入Model中,并返回login视图,显示登录页面,并提示用户名或密码错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值