SpringMVC4-组件(三)-ViewResolver

《看透springMvc源代码分析与实践》学习笔记
SpringMVC 版本 4.1.5.RELEASE

View Resolver

ViewResolver 是根据视图名和Locale解析出视图。SpringMVC中ViewResolver的结构如下图:
SpringMVC4-组件(三)-ViewResolver_第1张图片

除了AbstractCachingViewResolver一家独大,其他三类只有它们自身一个实现类.

  • BeanNameViewResolver:使用beanName从SpringMVC容器里查找bean.
  • ViewResolverComposite: 它内部有一成员变量List viewResolvers,在初始化时需要对该变量进行设置.解析时for-each viewResolvers,任一解析成功,立即返回。
  • ContentNegotiatingViewResolver 和 AbstractCachingViewResolver 比较复杂,后面单独分析。

ContentNegotiatingViewResolver

ContentNegotiatingViewResolver解析器的作用是在别的解析器的结果上增加了对MediaType(也叫Content-Type)后缀名的支持。
整个处理过程:

  • 首先遍历容器中所有的viewResolvers-bean
  • 使用request获取MediaType,然后与viewResolvers匹配查找出最优的视图。

开发者可用通过向spring容器注入viewResolver,来实现对不同“模板引擎”的支持.
如:ThymeleafViewResolver,FreeMarkerViewResolver等。

viewResolvers初始化
viewResolvers初始化包含两部分:

  • a. 通过viewResolvers属性手动配置
  • b. spring容器中,且不在手动配置列表中
//org.springframework.web.servlet.view.ContentNegotiatingViewResolver
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
    private List viewResolvers;

	@Override
	protected void initServletContext(ServletContext servletContext) {
	    //取出Spring容器中所有的ViewResolver (注意不是SpringMVC容器)
		Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
		//若没有手动设置的viewResolvers, 直接添加容器中的ViewResolver
		if (this.viewResolvers == null) {
			this.viewResolvers = new ArrayList(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
			    //排除自身
				if (this != viewResolver) {
					this.viewResolvers.add(viewResolver);
				}
			}
		}else {
		    
            for (int i=0; i < viewResolvers.size(); i++) {
                if (matchingBeans.contains(viewResolvers.get(i))) {
                    continue;
                }
                String name = viewResolvers.get(i).getClass().getName() + i;
                //如果手动设置的viewResolver,没有在容器中,则进行初始化
                getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
            }
        }
        
        //排序
		OrderComparator.sort(this.viewResolvers);
		this.cnManagerFactoryBean.setServletContext(servletContext);
	}
}

resolveViewName解析视图

//org.springframework.web.servlet.view.ContentNegotiatingViewResolver
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
    //获取RequestAttributes
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
    
    //获取满足条件的MediaType
    List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
    if (requestedMediaTypes != null) {
        //根据viewName + requestedMediaTypes 以及内部使用后缀名 查找 候选列表candidateViews
        List candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
        
        //选出最优视图:: 如果有redirect视图直接返回,否则从candidateViews列表中遍历匹配
        View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    if (this.useNotAcceptableStatusCode) {
        return NOT_ACCEPTABLE_VIEW;
    }
    return null;
}

AbstractCachingViewResolver

AbstractCachingViewResolver 提供了统一的缓存功能,当视图解析过一次就被缓存起来,直到缓存被删除前视图的解析都会自动从缓存中获取。

public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
    public static final int DEFAULT_CACHE_LIMIT = 1024;
    private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
    private final Map viewAccessCache = new ConcurrentHashMap(DEFAULT_CACHE_LIMIT);
    
     //DEFAULT_CACHE_LIMIT=0 表示不开启removeEldestEntry;
  	private final Map viewCreationCache =
        new LinkedHashMap(DEFAULT_CACHE_LIMIT, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                if (size() > getCacheLimit()) {
                    viewAccessCache.remove(eldest.getKey());
                    return true;
                }
                else {
                    return false;
                }
            }
        };
        
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!isCache()) {
            return createView(viewName, locale);
        }else {
            Object cacheKey = getCacheKey(viewName, locale);
            View view = this.viewAccessCache.get(cacheKey);
            if (view == null) {
                synchronized (this.viewCreationCache) {
                    view = this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                        //createView可被子类重写
                        view = createView(viewName, locale);
                        if (view == null && this.cacheUnresolved) {
                            view = UNRESOLVED_VIEW;
                        }
                        if (view != null) {
                            this.viewAccessCache.put(cacheKey, view);
                            this.viewCreationCache.put(cacheKey, view);
                        }
                    }
                }
            }
            return (view != UNRESOLVED_VIEW ? view : null);
        }
    }
    
    //createView默认实现逻辑:调用子类loadView
    protected View createView(String viewName, Locale locale) throws Exception {
        return loadView(viewName, locale);
    }
    
    //模板方法
    protected abstract View loadView(String viewName, Locale locale) throws Exception;
}

AbstractCachingViewResolver类图

SpringMVC4-组件(三)-ViewResolver_第2张图片

它的直接继承类有三个:

  • ResourceBundleViewResolver:加载properties属性配置文件。

默认值为views,可以配置baseName或baseNames,
最终使用${baseName}-${locacle}配置文件。
例: 如使用默认baseName,locale为zh, 则会加载 “views-zh.properties”

  • XmlViewResolver: 加载xml文件,默认位置为"/WEB-INF/views.xml",可以通过location参数配置。
  • UrlBasedViewResolver: 比较复杂,下面详细介绍。

UrlBasedViewResolver

UrlBasedViewResolver重写了父类的getCacheKey,createView和loadView方法。

//org.springframework.web.servlet.view.UrlBasedViewResolver
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
    //prefix,suffix
    private String prefix = "";
    private String suffix = "";
    
    //可以使用正则表达式:如 add*Page
    private String[] viewNames;
    
    public static final String REDIRECT_URL_PREFIX = "redirect:";
    public static final String FORWARD_URL_PREFIX = "forward:";
    
	@Override
	protected Object getCacheKey(String viewName, Locale locale) {
	    //父类中 return viewName + "_" + locale; //由此得知UrlBasedViewResolver 不支持Locale
		return viewName;
	}
	
	@Override
    protected View createView(String viewName, Locale locale) throws Exception {
        // canHandle逻辑:如果viewNames为空,或viewNames数组(可以为正则)匹配viewName,则返回true
        if (!canHandle(viewName, locale)) {
            return null;
        }
        // redirect视图
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
            return applyLifecycleMethods(viewName, view);
        }
         // forward视图
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return new InternalResourceView(forwardUrl);
        }
        
        //如果都不是,则调用父类createView方法,而父类createView逻辑是调用模板方法:loadView
        return super.createView(viewName, locale);
    }
    
    @Override
    protected View loadView(String viewName, Locale locale) throws Exception {
        //创建具体的View
        AbstractUrlBasedView view = buildView(viewName);
        
        //调用applyLifecycleMethods,初始化view
        View result = applyLifecycleMethods(viewName, view);
        
        //检验view 对应的模板是否存在
        return (view.checkResource(locale) ? result : null);
    }

    //获取beanFactory,初始化bean,并返回View
    private View applyLifecycleMethods(String viewName, AbstractView view) {
        return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
    }
}

buildView

//org.springframework.web.servlet.view.UrlBasedViewResolver
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    //创建view
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
    //拼接 url
    view.setUrl(getPrefix() + viewName + getSuffix());
    //如果设置了contentType, 这里设置contentType
    String contentType = getContentType();
    if (contentType != null) {
        view.setContentType(contentType);
    }

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());

    //标识 view是否可以使用 @PathVariables 注释的参数。
    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }
    
    //标识 view是否允许使用容器中注册的bean
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    
    //标识 view能够使用容器中哪些beans
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
}

//viewClass必须满足一定的条件
public void setViewClass(Class viewClass) {
    if (viewClass == null || !requiredViewClass().isAssignableFrom(viewClass)) {
        throw new IllegalArgumentException(".........");
    }
    this.viewClass = viewClass;
}

//UrlBasedViewResolver中默认使用 AbstractUrlBasedView.class ,它的各个子类可以通过重写该方法使用不同的类型;
protected Class requiredViewClass() {
    return AbstractUrlBasedView.class;
}

UrlBasedViewResolver的子类们

UrlBasedViewResolver的子类主要做如下三件事:

  1. 重写requiredViewClass,指定特定类型的viewClass
  2. 使用setViewClass设置指定的viewClass
  3. 给创建的视图设置一些特别属性。

InternalResourceViewResolver

//org.springframework.web.servlet.view.InternalResourceViewResolver
public class InternalResourceViewResolver extends UrlBasedViewResolver {
    //是否jstl
	private static final boolean jstlPresent = ClassUtils.isPresent(
			"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
    
    //是否一致允许使用inclue: 默认为false
	private Boolean alwaysInclude;
	
    public InternalResourceViewResolver() {
        Class viewClass = requiredViewClass();
        if (viewClass.equals(InternalResourceView.class) && jstlPresent) {
            viewClass = JstlView.class;
        }
        setViewClass(viewClass);
    }
    
    
    //指定InternalResourceView类型
    @Override
    protected Class requiredViewClass() {
        return InternalResourceView.class;
    }
    
    @Override
    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;
    }
}

AbstractTemplateViewResolver

//org.springframework.web.servlet.view.AbstractTemplateViewResolver
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {
    //是否将所有requestAttributes 暴露给view
	private boolean exposeRequestAttributes = false;
    
    //如果requestAttributes中与model存在同名参数,是否允许使用requestAttributes中的值将model的值覆盖
	private boolean allowRequestOverride = false;

    //是否将所有session 暴露给view
	private boolean exposeSessionAttributes = false;
    
    //如果sessionAttributes中与model存在同名参数,是否允许使用sessionAttributes中的值将model的值覆盖
	private boolean allowSessionOverride = false;
    
    //是否将RequestContext暴露给view为spring的宏使用??
	private boolean exposeSpringMacroHelpers = true;
	
}

SpringMVC默认支持的模板有:

  • FreeMarkerViewResolver
  • VelocityViewResolver
  • GroovyMarkupViewResolver

View

通过ViewResolver解析出View之后,最终会调用view.render渲染,来响应此次请求.

public interface View {
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
	String getContentType();

	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}



RequestToViewNameTranslator

RequestToViewNameTranslator 可以在处理器返回的view为时使用它,然后根据request获取viewName;
SpringMVC提供的实现类只有一个:DefaultRequestToViewNameTranslator

DefaultRequestToViewNameTranslator

public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
	private static final String SLASH = "/";
	private String prefix = "";
	private String suffix = "";
	private String separator = SLASH;
	
	//剔除 开头、结尾部分的separator ,以及剔除后缀名 标识
    private boolean stripLeadingSlash = true;
    private boolean stripTrailingSlash = true;
    private boolean stripExtension = true;
    
    @Override
    public String getViewName(HttpServletRequest request) {
        //获取请求url
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        return (this.prefix + transformPath(lookupPath) + this.suffix);
    }

    //剔除 开头、结尾部分的separator ,以及剔除后缀名
    protected String transformPath(String lookupPath) {
        String path = lookupPath;
        if (this.stripLeadingSlash && path.startsWith(SLASH)) {
            path = path.substring(1);
        }
        if (this.stripTrailingSlash && path.endsWith(SLASH)) {
            path = path.substring(0, path.length() - 1);
        }
        if (this.stripExtension) {
            path = StringUtils.stripFilenameExtension(path);
        }
        
        if (!SLASH.equals(this.separator)) {
            path = StringUtils.replace(path, SLASH, this.separator);
        }
        return path;
    }
}

你可能感兴趣的:(springMVC)