资讯专栏INFORMATION COLUMN

SpringMVC之源码分析--ViewResolver(一)

pf_miles / 905人阅读

摘要:概述本章开始进入另一重要的组件,即视图组件,处理视图组件使用两个主要的接口是和。接口的作用是用于处理视图进行渲染。延用之前的介绍流程,本章分两部分进行阐述启动初始化和请求处理。

概述

本章开始进入另一重要的组件,即视图组件,Spring MVC处理视图组件使用两个主要的接口是ViewResolver和View。根据名称可知,ViewResolver即视图解析器,其作用是把逻辑视图名称解析为真正的视图,然后通过View对象进行渲染。View接口的作用是用于处理视图进行渲染。

延用之前的介绍流程,本章分两部分进行阐述:启动初始化和请求处理。

本系列文章是基于Spring5.0.5RELEASE。

ViewResolver初始化

Spring MVC初始化视图解析器策略与初始化其他策略一样,其入口是DispatcherSerlvet的initStrategies(context)方法,代码如下:

/**
 *初始化策略对象
 */
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    // 初始化视图解析器ViewResolver
    initViewResolvers(context);
    initFlashMapManager(context);
}

看过之前文章的可以了解,此方法是Spring MVC初始化策略组件的入口。针对视图解析器组件,调用initViewResolver(context)方法,加载视图处理策略,该方法源码如下:

private void initViewResolvers(ApplicationContext context) {
    // viewResolvers是视图解析器集合,接收到用户请求时,从该属性变量中获取到Spring MVC使用的视图解析器
    this.viewResolvers = null;
    // 是否从Spring上下文中加载ViewResolver,detectAllViewResolvers属性变量默认为true,可在web部署描述文件中修改
    if (this.detectAllViewResolvers) {
        // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
        // 按接口类型ViewResolver查找全部
        // key为bean的id(name),value为bean的class对象
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            // 对ViewResolver进行排序,通过Ordered接口实现
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else { 
        try {
            // 从Spring上下文中加载指定名字为"viewResolver"的bean,作为视图解析器
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we"ll add a default ViewResolver later.
        }
    }

    // Ensure we have at least one ViewResolver, by registering
    // a default ViewResolver if no other resolvers are found.
    // 为了确保至少有一个ViewResolver视图解析器,Spring MVC配置了默认的ViewResolver
    // 在DispatcherServlet.properties文件中定义,默认为InternalResourceViewResolver
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No ViewResolvers found in servlet "" + getServletName() + "": using default");
        }
    }
}

至此,Spring MVC即完成加载初始化ViewResolver视图解析器,即Spring MVC以具备解析处理逻辑视图名称的能力。

ViewResolver使用

ViewResolver的使用是指用户发起请求到Spring,Spring MVC经过HandlerMapping查找处理的handler,然后通过HandlerAdapter进行适配后处理用户请求,返回ModelAndView,最后使用ViewResolver对ModelAndView进行解析,即把逻辑视图名解析为真正的视图对象,由视图对象进行渲染的过程。

用户的请求处理流程由DispatcherServlet的doDispatcher(request,response)方法进行控制,主要代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    ... ...
    // 找到请求对应的handler
    mappedHandler = getHandler(processedRequest);
    ... ...
    // 找到对应handler的适配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ... ...
    // 执行用户请求的拦截器前置处理方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // 调用实际的handler处理方法
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    ... ...
    // 设置默认的逻辑视图名称
    // 如果handler返回null或者ViewName属性为null时,Spring进行取视图规则为:前缀+url+后缀
    // 比如访问url为:http://localhost:8086/test,前缀配置/WEB-INF/jsp,后缀配置为.jsp,那么最终查找的是:/WEB-INF/jsp/test.jsp
    applyDefaultViewName(processedRequest, mv);
    // 执行用户请求的拦截器后置处理方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ... ...
    // 
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

/*
 *处理结果
 */
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?
    // 调用render方法
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name "" + getServletName() +
                    "": assuming HandlerAdapter completed request handling");
        }
    }

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

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

/*
 *渲染ModelAndView
 */
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对象
        // 此处是视图解析器使用入口,见下面的方法
        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.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name "" + getServletName() + """);
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 调用View的render方法进行视图渲染
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name "" +
                    getServletName() + """, ex);
        }
        throw ex;
    }
}

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

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 调用ViewResovler接口入口方法
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}
总结

本章概述了Spring MVC加载视图解析器策略,并且找到了ViewResolver的入口方法,下面的章节继续学习ViewResolver接口的实现类。

最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69637.html

相关文章

  • SpringMVC源码分析--ViewResolver(三)

    摘要:概述本节学习下的功能,简单来说,该类的作用就是把多个视图解析器进行组装,内部使用存储配置使用的视图解析器。总结本章介绍了类,根据测试,了解到属性不影响中配置使用的视图解析器顺序。 概述 本节学习下ViewResolverComposite的功能,简单来说,该类的作用就是把多个ViewResolver视图解析器进行组装,内部使用list存储配置使用的视图解析器。 本系列文章是基于Spri...

    fox_soyoung 评论0 收藏0
  • SpringMVC源码分析--ViewResolver(二)

    摘要:概述上篇学习了视图解析器作用及处理流程,为我们提供了丰富的视图解析器见下图本系列文章是基于。该视图解析器是根据处理器返回的逻辑视图名称,在应用上下文中查找该名称的视图对象视图对象就是的对象。 概述 上篇学习了Spring MVC ViewResolver视图解析器作用及处理流程,Spring为我们提供了丰富的视图解析器(见下图):showImg(https://segmentfault...

    jas0n 评论0 收藏0
  • SpringMVC源码分析--ViewResolver(四)

    摘要:概述本章继续学习另一个实现类解析器,该类的主要作用是根据同一请求的某些策略,选择对应的进行渲染。可以把理解为适配器,对不同类型进行适配。值得注意的是处理的为同一个。本系列文章是基于。实战需求目标实现后缀名或参数控制,显示不同的视图。 概述 本章继续学习ViewResolver另一个实现类ContentNegotiatingViewResolver解析器,该类的主要作用是根据同一请求的某...

    jcc 评论0 收藏0
  • SpringMVC源码分析--ViewResolver(六)

    摘要:与一样,该类继承抽象类,并且通过外部的属性文件定义逻辑视图名称与真正的视图对象的关系,属性文件默认是下的,可以通过或属性来指定,该属性指的是文件的基名称,也就是说以属性值开头的属性文件。 概述 本章再学习另外两个ViewResolver,分别是XmlViewResolver和ResourceBundleViewResolver,从功能上说,这两个视图解析器都是从外部资源文件中查找视图V...

    alighters 评论0 收藏0
  • SpringMVC源码分析--ViewResolver(五)

    摘要:此解析器与差不多,更改下配置文件中的类全路径即可。总结本章介绍了以及三个视图解析器。这部分内容有点儿多,我会尽快结束。 概述 通过上几篇的学习,我们分析了并试验了ViewResolverComposite、BeanNameViewResolver和ContentNegotiatingViewResolver,这三个类都直接实现ViewResolver接口。Spring MVC提供了很多...

    klinson 评论0 收藏0

发表评论

0条评论

pf_miles

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<