spring源码解析-web系列(六):九大组件之ViewResolver

吕翰飞
2023-12-01

spring源码解析-web系列(一):启动
spring源码解析-web系列(二):处理请求的过程
spring源码解析-web系列(三):九大组件之HandlerMapping
spring源码解析-web系列(四):九大组件之HandlerAdapter
spring源码解析-web系列(五):解析请求参数
spring源码解析-web系列(六):九大组件之ViewResolver
spring源码解析-web系列(七):九大组件之HandlerExceptionResolver

转载请标明出处:
https://blog.csdn.net/bingospunky/article/details/98622667
本文出自马彬彬的博客

前言

ViewResolver的作用是通过ViewName获取到View,从而可以渲染结果。ViewResolver的接口定义如下:

代码1 (org.springframework.web.servlet.ViewResolver):

public interface ViewResolver {
    View resolveViewName(String var1, Locale var2) throws Exception;
}

ViewResolver的子类可以分为如下四类:
1.AbstractCachingViewResolver:提供了抽象缓存的能力,当缓存里不存在时,通过模板方法让子类创建。
2.BeanNameViewResolver:通过name去BeanFactory获取对象。简单至极,不予描述。
3.ContentNegotiatingViewResolver:内部分装了一些ViewResolver,具体工作通过内部封装的ViewResolver获取View,当获取到多个View时,该类通过MediaTypes(Content-type)选出一个最合适的View。
4.ViewResolverComposite:内部封装了一些ViewResolver,自己不干活,通过内部的ViewResolver干活。

AbstractCachingViewResolver

代码2 (org.springframework.web.servlet.view.AbstractCachingViewResolver.resolveViewName):

	public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!this.isCache()) {
            return this.createView(viewName, locale);
        } else {
            Object cacheKey = this.getCacheKey(viewName, locale);
            View view = (View)this.viewAccessCache.get(cacheKey);
            if (view == null) {
                Map var5 = this.viewCreationCache;
                synchronized(this.viewCreationCache) {
                    view = (View)this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                        view = this.createView(viewName, locale);
                        if (view == null && this.cacheUnresolved) {
                            view = UNRESOLVED_VIEW;
                        }

                        if (view != null) {
                            this.viewAccessCache.put(cacheKey, view);
                            this.viewCreationCache.put(cacheKey, view);
                            if (this.logger.isTraceEnabled()) {
                                this.logger.trace("Cached view [" + cacheKey + "]");
                            }
                        }
                    }
                }
            }

            return view != UNRESOLVED_VIEW ? view : null;
        }
    }

代码2主要就是对缓存的操作,如果缓存里有,直接从缓存里取;如果缓存里没有,那么通过代码2第12行的createView方法创建,createView又调用了模板方法loadView让子类创建。

这里使用了两个Map做缓存,viewAccessCache是ConcurrentHashMap类型的,主要用来做检索缓存,线程安全的且效率高;viewCreationCache是LinkedHashMap类型的,主要用来控制缓存数量限制,如果做线程安全,它的效率不如ConcurrentHashMap,所以存在了LinkedHashMap的基础上又使用了ConcurrentHashMap。

UrlBasedViewResolver

UrlBasedViewResolver继承AbstractCachingViewResolver,实现createView方法和loadView方法来创建具体的View。

代码3 (org.springframework.web.servlet.view.UrlBasedViewResolver.createView):

	protected View createView(String viewName, Locale locale) throws Exception {
        if (!this.canHandle(viewName, locale)) {
            return null;
        } else {
            String forwardUrl;
            if (viewName.startsWith("redirect:")) {
                forwardUrl = viewName.substring("redirect:".length());
                RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
                return this.applyLifecycleMethods(viewName, view);
            } else if (viewName.startsWith("forward:")) {
                forwardUrl = viewName.substring("forward:".length());
                return new InternalResourceView(forwardUrl);
            } else {
                return super.createView(viewName, locale);
            }
        }
    }

代码3 UrlBasedViewResolver重写父类的createView,对 redirectforward 类型的ViewName进行了处理,创建了对应的View返回。否则还是调用父类的createView方法进行处理。

代码4 (org.springframework.web.servlet.view.UrlBasedViewResolver):

	protected View loadView(String viewName, Locale locale) throws Exception {
        AbstractUrlBasedView view = this.buildView(viewName);
        View result = this.applyLifecycleMethods(viewName, view);
        return view.checkResource(locale) ? result : null;
    }
    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(this.getViewClass());
        view.setUrl(this.getPrefix() + viewName + this.getSuffix());
        String contentType = this.getContentType();
        if (contentType != null) {
            view.setContentType(contentType);
        }

        view.setRequestContextAttribute(this.getRequestContextAttribute());
        view.setAttributesMap(this.getAttributesMap());
        Boolean exposePathVariables = this.getExposePathVariables();
        if (exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }

        Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
            view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }

        String[] exposedContextBeanNames = this.getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
            view.setExposedContextBeanNames(exposedContextBeanNames);
        }

        return view;
    }

代码4的loadView方法是父类的模板方法,用来创建具体和View。代码4第2行调用了this.buildView创建AbstractUrlBasedView。代码4第7行根据该ViewResolver里保存的ViewClass创建对象,代码4第8~第29行对这个对象设置一些参数。

至此,创建View的代码已经在UrlBasedViewResolver类中了,那么它的子类主要做的就是如下两件事情了:1.使用setViewClass设置对应的View类型(重写requiredViewClass对View’类型的限制)。2.给创建出来的View设置属性。

InternalResourceViewResolver

代码5 (org.springframework.web.servlet.view.InternalResourceViewResolver):

public class InternalResourceViewResolver extends UrlBasedViewResolver {
    private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
    private Boolean alwaysInclude;

    public InternalResourceViewResolver() {
        Class<?> viewClass = this.requiredViewClass();
        if (viewClass.equals(InternalResourceView.class) && jstlPresent) {
            viewClass = JstlView.class;
        }

        this.setViewClass(viewClass);
    }

    protected Class<?> requiredViewClass() {
        return InternalResourceView.class;
    }

    public void setAlwaysInclude(boolean alwaysInclude) {
        this.alwaysInclude = alwaysInclude;
    }

    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        InternalResourceView view = (InternalResourceView)super.buildView(viewName);
        if (this.alwaysInclude != null) {
            view.setAlwaysInclude(this.alwaysInclude);
        }

        view.setPreventDispatchLoop(true);
        return view;
    }
}

代码5第6第11行,在InternalResourceViewResolver的构造方法中,设置了ViewClass。代码5第23第29行代码中创建InternalResourceView对象,并且设置一些参数。

总结

ViewResolver的作用是通过ViewName获取到View,从而可以渲染结果。主要有AbstractCachingViewResolver、BeanNameViewResolver、ContentNegotiatingViewResolver、ViewResolverComposite,本文只描述了AbstractCachingViewResolver这个系列。

注意并不是每一个请求都会使用ViewResolver去获取View。我在 spring源码解析-web系列(四):九大组件之HandlerAdapter 这篇博客的ServletInvocableHandlerMethod这部分有提到使用returnValueHandler来处理返回值,并根据情况去渲染。如果是returnValueHandler没有渲染的,才会使用ViewResolver获取View来渲染;如果已经被returnValueHandler渲染过,那么不会再使用ViewResolver处理。

 类似资料: