springmvc--11--ViewResolver和View

springmvc–ViewResolverView

1 前言

虽然现在前后端分离是主流,但是我们还是需要了解一下传统项目。当我们在处理器方法中返回一个String字符串并且没有使用@ResponseBody注解的时候,springmvc就会使用视图解析器解析这个String字符串得到视图对象,最终调用视图对象的render()方法完成视图渲染,响应给客户端。

2 视图解析和响应原理

经过前面的学习,我们知道DispatcherServlet在它的doDispatch(request, response)方法中完成请求的处理和响应,下面是该方法的源码

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            //从处理器映射器中得到处理器执行链
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            //获取支持该处理器的处理器适配器
            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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            //执行拦截器applyPreHandle()方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            //处理器适配器调用处理器方法, 并返回一个ModelAndView对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            //没有指定视图名,则使用默认的视图名
            applyDefaultViewName(processedRequest, mv);
            //执行拦截器applyPostHandle()方法
            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);
        }
        //处理异常和ModelAndView对象,使用视图解析器解析视图,响应视图
        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);
            }
        }
    }
}

我们重点看一下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);方法,该方法调用视图解析器解析视图并响应给客户端

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
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 {
            //解析异常,生成一个ModelAndView对象
            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()) {
        //解析ModelAndView对象,生成视图对象,渲染视图,响应给客户端浏览器
        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(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)方法

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
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("Could not resolve view with name '" + mv.getViewName() +
                                       "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        //获取视图对象
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                                       "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        //调用视图对象的render()方法完成视图渲染,响应给客户端浏览器
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

其实视图解析原理很简单:

处理器方法返回一个String字符串,这个字符串被视图解析器解析,生成视图对象,最后调用视图对象的render()方法渲染视图,响应给浏览器。

3 视图解析器

先来看一下springmvc定义的视图解析器接口

public interface ViewResolver {

    /**
     * 解析视图名,得到视图对象
     */
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;

}

这个接口只有一个方法,就是根据视图名得到视图对象。我们再看一下springmvc提供的默认的视图解析器

在这里插入图片描述

上面这些就是springmvc提供的默认的视图解析器

视图解析器作用
UrlBasedViewResolver解析得到转发和重定向视图
InternalResourceViewResolver解析得到jsp视图
ContentNegotiatingViewResolver根据客户端可接收的内容类型调用其他的视图解析器创建视图对象
FreeMarkerViewResolver解析得到FreeMaker模板视图
GroovyMarkupViewResolver解析得到Groovy XML/XHTML模板视图
BeanNameViewResolver根据视图名从spring容器中获取对应的视图对象
ResourceBundleViewResolverBeanNameViewResolver很相似,只是该视图解析器只会默认获取views包定义的视图bean对象
XmlViewResolverBeanNameViewResolver很相似,只是该视图解析器只会默认获取/WEB-INF/views.xml文件定义的视图bean对象

视图解析器只负责将视图名解析为对应的视图对象,视图渲染过程由视图对象的render()方法完成

4 视图

先来看一下springmvc定义的视图接口

public interface View {

    /**
     * 响应状态码
     * org.springframework.web.servlet.View.responseStatus
     */
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

    /**
     * 模板变量
     * org.springframework.web.servlet.View.pathVariables
     */
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";

    /**
     * 选中的视图类型
     * org.springframework.web.servlet.View.selectedContentType
     */
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";


    //获取响应的内容类型
    @Nullable
    default String getContentType() {
        return null;
    }

    //渲染视图
    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
        throws Exception;

}

这个接口的核心方法就是render()方法,它负责将视图内容写入到响应体中。另外还定义了3个常量

  • RESPONSE_STATUS_ATTRIBUTE
  • PATH_VARIABLES
  • SELECTED_CONTENT_TYPE

在这里插入图片描述

上面这些就是springmvc提供的默认的视图

视图解析器作用
InternalResourceViewjsp视图
JstlView可解析jstl标签的jsp视图
RedirectView重定向视图
MappingJackson2JsonView响应json数据的视图
MappingJackson2XmlView响应xml数据的视图
FreeMarkerView可解析FreeMarkerView标签的视图
GroovyMarkupView可解析Groovy标签的视图
AbstractXlsxViewexcel表格下载的视图,需要用户自己实现具体逻辑

springmvc提供很多默认的视图类型,用户可以根据需要选择合适的视图响应

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值