前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图解析器ViewResolver详解

【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图解析器ViewResolver详解

作者头像
YourBatman
发布2019-09-03 15:50:00
8300
发布2019-09-03 15:50:00
举报
文章被收录于专栏:BAT的乌托邦

前言

Spring的一个优秀之处在于,把view层技术与MVC框架的其他部分离开来。 例如,选择使用Velocity或者XSLT来代替已有的JSP方式只需要修改配置就可以实现。

前面已经讲解了Spring MVC对Handler返回值的处理:

【小家Spring】Spring MVC容器的web九大组件之—HandlerAdapter源码详解—一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler

我们知道,当我们对SpringMVC控制的资源发起请求时,这些请求都会被SpringMVCDispatcherServlet处理。接着它会根据请求的URL经过HandlerMapping处理,匹配上一个最合适的HandlerExecutionChain(它是一个拦截器+handler的组合)。

然后再通过Handler拿到一个HandlerAdapterHandlerAdapter再对Handler进行执行、处理之后会统一返回一个ModelAndView对象。

在获得了ModelAndView对象之后,SpringMVC就需要把该View渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是ViewResolverView,本文就是讲解ViewResolver

Handler返回的ModelAndView中不包含真正的视图,只返回一个逻辑视图(比如返回一个字符串)名称的时候,ViewResolver就会把该逻辑视图名称解析为真正的视图**View**对象

View是真正的进行视图渲染(对response里写东西),把结果返回给浏览器的

ViewResolver

SpringMVC 用于处理视图最重要的两个接口是 ViewResolverViewViewResolver 的主要作用是把一个逻辑上的视图名称解析为一个真正的视图(View )SpringMVC 中用于把 View 对象呈现给客户端的是 View 对象本身,而 ViewResolver 只是把逻辑视图名称解析为对象的View对象。 View 接口的主要作用是用于处理视图,然后返回给客户端。

Spring MVC为我们定义了非常多的视图解析器,下面重点就是看看该接口本身以及它的实现类们:

代码语言:javascript
复制
// 这个接口非常简单,就一个方法:把一个逻辑视图viewName解析为一个真正的视图View,Local表示国际化相关内容~
public interface ViewResolver {
	@Nullable
	View resolveViewName(String viewName, Locale locale) throws Exception;
}

看看它的继承树:

此处需要注意的是,我上面的截图用的是Spring5.x版本,下面我截图一个Spring4.x的作为对比:

可以看到曾经非常火的页面渲染技术:velocitySpring5里面已经被完全抛弃了。根本原因在于velocity社区太不活跃了,上十年都不更新。虽然2018年左右社区又启动了维护,但显然已经不能让Spring回头了

在Spring4.x版本中虽然没有删除掉Velocity的包,但也都标记为过时了~~~

关于Apache的title技术,我今天刚打开官网,却发现一行大红字:

看来它也寿终正寝了,现在处于交替期,不建议大家在新项目中使用了。


现在推荐使用新一代高性能渲染引擎:Thymeleaf,这也是Spring(Boot)的推荐~


AbstractCachingViewResolver 非常重要

这是一个抽象类,这种视图解析器会把它曾经解析过的视图缓存起来(从命名caching也能看出来)。然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的 map 中,接着再把新建的视图返回

使用这种视图缓存的方式可以把解析视图的性能问题降到最低,所以它是Spring MVC最为主要的渲染方式

代码语言:javascript
复制
// 该首相类完成的主要是缓存的相关逻辑~~~
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
	
	// Map的最大值,1024我觉得还是挺大的了~
	/** Default maximum number of entries for the view cache: 1024. */
	public static final int DEFAULT_CACHE_LIMIT = 1024;

	// 表示没有被解析过的View~~~
	private static final View UNRESOLVED_VIEW = new View() {
		@Override
		@Nullable
		public String getContentType() {
			return null;
		}
		@Override
		public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
		}
	};
	private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
	private boolean cacheUnresolved = true;
	// 此处使用的是ConcurrentHashMap,key是Object
	private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);

	// 通过它来实现缓存最大值: removeEldestEntry表示当你往里put成为为true的时候,会执行它
	// 此处可以看到,当size大于1024时,会把Map里面最老的那个值给remove掉~~~viewAccessCache.remove(eldest.getKey());
	private final Map<Object, View> viewCreationCache =
			new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
				@Override
				protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
					if (size() > getCacheLimit()) {
						viewAccessCache.remove(eldest.getKey());
						return true;
					}
					else {
						return false;
					}
				}
			};
	
	...

	// 通过逻辑视图,来找到一个View真正的视图~~~~
	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			return createView(viewName, locale);
		} else {
			// cacheKey其实就是 viewName + '_' + locale
			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) {
						// Ask the subclass to create the View object.
						// 具体的创建视图的逻辑  交给子类的去完成~~~~
						view = createView(viewName, locale);
						// 此处需要注意:若调用者返回的是null,并且cacheUnresolved,那就返回一个未经处理的视图~~~~
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						// 缓存起来~~~~
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
		
			// 这个很重要,因为没有被解析过  都会返回null
			// 而再真正责任链处理的时候,第一个不返回null的view,最终就会被返回了~~~
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

	// 逻辑比较简单~~~
	public void removeFromCache(String viewName, Locale locale) {
		...
	}
	public void clearCache() {
		logger.debug("Clearing all views from the cache");
		synchronized (this.viewCreationCache) {
			this.viewAccessCache.clear();
			this.viewCreationCache.clear();
		}
	}
}

课件此抽象类完成的是缓存相关的维护逻辑,而子类只需要专注在createView这件事情上了。

UrlBasedViewResolver

它是对 ViewResolver 的一种简单实现,而且继承了AbstractCachingViewResolver ,主要就是提供的一种拼接 URL 的方式来解析视图,它可以让我们通过 prefix 属性指定一个指定的前缀,通过 suffix 属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图 URL 了。

如 prefix=/WEB-INF/jsps/ , suffix=.jsp ,返回的视图名称 viewName=test/indx ,则 UrlBasedViewResolver 解析出来的视图 URL 就是 /WEB-INF/jsps/test/index.jsp

代码语言:javascript
复制
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
	//  ”redirect:” 前缀 包装成一个RedirectView  最终调用 HttpServletResponse 对象的 sendRedirect 方法进行重定向
	public static final String REDIRECT_URL_PREFIX = "redirect:";
	//  forword: 前缀的视图名称将会被封装成一个 InternalResourceView 对象  服务器端利用 `RequestDispatcher`的forword方式跳转到指定的地址
	public static final String FORWARD_URL_PREFIX = "forward:";

	// 这个三个属性是最重要的~~~
	@Nullable
	private Class<?> viewClass;
	private String prefix = "";
	private String suffix = "";

	// 其它属性值非常多
	// the content type for all views,若view自己设置了此值就用自己的,否则是它
	@Nullable
	private String contentType;
	//重定向的时候,是否把/解释为相对当前ServletContext的路径
	// 直接关系RedirectView#setContextRelative这个值
	private boolean redirectContextRelative = true;
	// 设置重定向是否应与HTTP 1.0客户端保持兼容
	private boolean redirectHttp10Compatible = true;
	// 配置与应用程序关联的一个或多个主机  @since 4.3
	@Nullable
	private String[] redirectHosts;
	// Set the name of the RequestContext attribute for all views
	@Nullable
	private String requestContextAttribute;

	/** Map of static attributes, keyed by attribute name (String). */
	// 保存一些全局属性~~~
	private final Map<String, Object> staticAttributes = new HashMap<>();

	// 指定此解析程序解析的视图是否应向模型添加路径变量
	// {@code true} - all Views resolved by this resolver will expose path variables
	// {@code false} - no Views resolved by this resolver will expose path variables
	// {@code null} - individual Views can decide for themselves (this is used by the default)  默认值是这个
	@Nullable
	private Boolean exposePathVariables;

	// 设置是否将应用程序上下文中的所有SpringBean作为请求属性进行访问
	// This will make all such beans accessible in plain {@code ${...}} expressions in a JSP 2.0 page, as well as in JSTL's {@code c:out} value expressions
	//AbstractView#setExposeContextBeansAsAttributes 默认值是false
	@Nullable
	private Boolean exposeContextBeansAsAttributes;
	// 在应该公开的上下文中指定bean的名称 如果不为空,则只有指定的bean才有资格作为属性进行暴露
	@Nullable
	private String[] exposedContextBeanNames;
	// Set the view names (or name patterns) that can be handled by this ViewResolver
	// View names can contain simple wildcards such that 'my*', '*Report' and '*Repo*' will all match the view name 'myReport'.
	@Nullable
	private String[] viewNames;

	// 你指定的viewClass必须是AbstractUrlBasedView的子类
	protected Class<?> requiredViewClass() {
		return AbstractUrlBasedView.class;
	}

	// 把Properties 保存起来,放在群居的map里
	public void setAttributes(Properties props) {
		CollectionUtils.mergePropertiesIntoMap(props, this.staticAttributes);
	}
	public void setAttributesMap(@Nullable Map<String, ?> attributes) {
		if (attributes != null) {
			this.staticAttributes.putAll(attributes);
		}
	}

	// 从这里可以看出viewClass属性,如果你在Spring容器里面使用,它是必须的~~~
	@Override
	protected void initApplicationContext() {
		super.initApplicationContext();
		if (getViewClass() == null) {
			throw new IllegalArgumentException("Property 'viewClass' is required");
		}
	}

	// 这个方法注意:复写的是父类的crateView方法,而不是loadView方法(loadView才是抽象方法~~~)注意这个涉及技巧~~~   分层次进行处理
	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		// canHandle表示:viewNames没配置  或者  匹配上了 就返回true
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// Check for special "redirect:" prefix.
		// 最终被转换成一个RedirectView,可以看到这里很多属性都是为它而准备的~~~比如getRedirectHosts这种属性值~~~
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" prefix.
		// forward打头的用的就是`InternalResourceView `
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// Else fall back to superclass implementation: calling loadView.
		return super.createView(viewName, locale);
	}

	// 执行容器内此Bean的声明周期方法,也就是view的声明周期方法。比如@Postconstruct、XXXAware这种方法
	// 可议看到它调用的是initializeBean,可议知道我们的View并不需要交给容器管理,但我们却能够享受它的一些声明周期方法~~~~~
	protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
		ApplicationContext context = getApplicationContext();
		if (context != null) {
			Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
			if (initialized instanceof View) {
				return (View) initialized;
			}
		}
		return view;
	}

	// 实现了父类的loadView方法~
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
		AbstractUrlBasedView view = buildView(viewName);
		View result = applyLifecycleMethods(viewName, view);
		
		// 这一步非常关键,它调用了view的checkResource方法,而这个方法的默认实现是永远返回true的
		// 所以请注意:特别是在你自定义视图的时候,注意重写此方法。只有资源真的存在的时候,你才去返回,否则让返回null,交给别的视图解析器继续去处理~~~
		// 自己处理不了的,自己就不要勉强了~~~~
		return (view.checkResource(locale) ? result : null);
	}

	// 构建一个View,注意此处的返回值为AbstractUrlBasedView~~ 合理主要工作就是把属性都设置进去~~~
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		// 我们必须配置的viewClass属性~~~~ 然后反射创建一个实例~~
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		view.setUrl(getPrefix() + viewName + getSuffix());

		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}
		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());
		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}
}

使用 UrlBasedViewResolver 的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView ,利用它来展现 jsp 。但是当我们要使用 JSTL 的时候我们必须使用 JstlViewJstlViewInternalResourceView的子类)

ScriptTemplateViewResolver

个脚本渲染有关的一个处理器。处理成ScriptTemplateView(自定义前缀、后缀)

代码语言:javascript
复制
// @since 4.2   是一个非常新的View处理器~~~
public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
	public ScriptTemplateViewResolver() {
		setViewClass(requiredViewClass());
	}
	public ScriptTemplateViewResolver(String prefix, String suffix) {
		this();
		setPrefix(prefix);
		setSuffix(suffix);
	}
	// ScriptTemplateView的父类是AbstractUrlBasedView
	@Override
	protected Class<?> requiredViewClass() {
		return ScriptTemplateView.class;
	}
}
InternalResourceViewResolver

这个视图处理器最为重要,它也是Spring MVC默认给装配的视图解析器

代码语言:javascript
复制
public class InternalResourceViewResolver extends UrlBasedViewResolver {

	// 如果你导入了JSTL的相关的包,这个解析器也会支持JSTLView的~~~~
	private static final boolean jstlPresent = ClassUtils.isPresent(
			"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());

	// 指定是否始终包含视图而不是转发到视图
	// 默认值为“false”。打开此标志以强制使用servlet include,即使可以进行转发
	// InternalResourceView#setAlwaysInclude
	@Nullable
	private Boolean alwaysInclude;

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

	// 默认情况下,它可能会设置一个JstlView 或者 InternalResourceView
	public InternalResourceViewResolver() {
		Class<?> viewClass = requiredViewClass();
		if (InternalResourceView.class == viewClass && jstlPresent) {
			viewClass = JstlView.class;
		}
		setViewClass(viewClass);
	}
	public InternalResourceViewResolver(String prefix, String suffix) {
		this(); // 先调用空构造
		setPrefix(prefix);
		setSuffix(suffix);
	}

	// 在父类实现的记仇上,设置上了alwaysInclude,并且view.setPreventDispatchLoop(true)
	@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;
	}

}

因为它是默认就被装配进去的,所以啥都不说了,这么写:

代码语言:javascript
复制
    @GetMapping("/index")
    public String index() {
        return "index.jsp";
    }

这样我们访问http://localhost:8080/demo_war_war/index就能顺利的展示这个页面了

理论上我们的JSP页面都应该放在WEB-INF目录下,避免直接访问。此处因为只是Demo,我就先不遵守了~

Spring提供了两种支持JSP视图的方式:

  1. InternalResourceViewResolver会将视图名解析为JSP文 件。另外,如果在你的JSP页面中使用了JSP标准标签库 (JavaServer Pages Standard Tag Library,JSTL)的 话,InternalResourceViewResolver能够将视图名解析为 JstlView形式的JSP文件,从而将JSTL本地化和资源bundle变量暴 露给JSTL的格式化(formatting)和信息(message)标签。
  2. Spring提供了两个JSP标签库,一个用于表单到模型的绑定,另一 个提供了通用的工具类特性。
  3. <%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>:绑定标签库。如:<sf:checkbox>、<sf:checkboxes>、<sf:errors>、<sf:form>、<sf:input>、<sf:select>...等等
  4. <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>:通用标签库。<s:bind>、<s:escapeBody>、<s:htmlEscape>、<s:message>、<s:url>、<s:url>...等等
XsltViewResolver

将视图名解析为一个指定XSLT样式表的URL文件。比如解析成Excel表格形式、 World形式等等。此处略~

AbstractTemplateViewResolver

继承自UrlBasedViewResolver,重写了buildView方法,主要就是构造AbstractTemplateView以及为它设置相应的属性。从命名中也能看出,它提供的是一种模版技术

代码语言:javascript
复制
// 模板视图解析程序的抽象基类,尤其是FreeMarker视图的抽象基类
// @since 1.1  对应的View是AbstractTemplateView
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {

	// 是否吧所有热request里面的attributes都加入合并到模版的Model,默认是false
	private boolean exposeRequestAttributes = false;
	// 是否允许request里面的属性,当name相同的时候,复写model里面的 默认是false
	private boolean allowRequestOverride = false;

	// session相关,语义同上
	private boolean exposeSessionAttributes = false;
	private boolean allowSessionOverride = false;

	// Set whether to expose a RequestContext for use by Spring's macro library 默认值是true
	private boolean exposeSpringMacroHelpers = true;

	// 它只会处理AbstractTemplateView 比如FreeMarkerView是它的实现类
	@Override
	protected Class<?> requiredViewClass() {
		return AbstractTemplateView.class;
	}

	// 模版操作:其实就是多设置了一些开关属性~~~~
	@Override
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
		view.setExposeRequestAttributes(this.exposeRequestAttributes);
		view.setAllowRequestOverride(this.allowRequestOverride);
		view.setExposeSessionAttributes(this.exposeSessionAttributes);
		view.setAllowSessionOverride(this.allowSessionOverride);
		view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
		return view;
	}
}

VelocityViewResolver也是继承自此AbstractTemplateViewResolver ThymeleafViewResolver并没有继承自AbstractTemplateViewResolver,而是直接继承AbstractCachingViewResolver

GroovyMarkupViewResolver

FreeMarkerViewResolver
代码语言:javascript
复制
	@Override
	protected Class<?> requiredViewClass() {
		return FreeMarkerView.class;
	}

逻辑很简单。

FreeMarker是个老牌的模版引擎,整体性能也还不错,所以一直以来口碑还不错。但在新时代的发展下,显然还是有点乏力了的~~~

BeanNameViewResolver

它是对ViewResolver的一个比较简单的实现,在Spring第一个版本就推出了。通过把返回的逻辑视图名称去匹配定义好的视图 bean 对象。(也就是说如果你返回的逻辑视图名称为test,那么它就会去容器内找到这个View,然后返回)

代码语言:javascript
复制
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {

	// 默认排序最小值~~~
	private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered
	public void setOrder(int order) {
		this.order = order;
	}
	@Override
	public int getOrder() {
		return this.order;
	}

	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws BeansException {
		// 可见它和容器强关联,若容器里没有这个Bean,他就直接返回null了~~~
		ApplicationContext context = obtainApplicationContext();
		if (!context.containsBean(viewName)) {
			// Allow for ViewResolver chaining...
			return null;
		}
		// 可见不仅仅要含有此Bean,还必须是view类型的~~~~  否则也是返回null
		if (!context.isTypeMatch(viewName, View.class)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found bean named '" + viewName + "' but it does not implement View");
			}
			return null;
		}
		// 拿出这个View就这直接返回了~~~
		return context.getBean(viewName, View.class);
	}
}

ViewResolverComposite

看过直接一篇文章:

【小家Spring】Spring MVC容器的web九大组件之—HandlerAdapter源码详解—一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler

里面讲过HandlerMethodReturnValueHandlerComposite,这个类就无需多说了。

代码语言:javascript
复制
// @since 4.1
public class ViewResolverComposite implements ViewResolver, Ordered, InitializingBean, ApplicationContextAware, ServletContextAware {

	private final List<ViewResolver> viewResolvers = new ArrayList<>();
	private int order = Ordered.LOWEST_PRECEDENCE;

	// 若直接set了,就以自己的set为主
	public void setViewResolvers(List<ViewResolver> viewResolvers) {
		this.viewResolvers.clear();
		if (!CollectionUtils.isEmpty(viewResolvers)) {
			this.viewResolvers.addAll(viewResolvers);
		}
	}

	// 为每一个实现了接口ApplicationContextAware的 都设置一个 下面还有其它的
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		for (ViewResolver viewResolver : this.viewResolvers) {
			if (viewResolver instanceof ApplicationContextAware) {
				((ApplicationContextAware)viewResolver).setApplicationContext(applicationContext);
			}
		}
	}
	...


	// 这是核心   遍历所有的viewResolvers,第一个不返回null的,就标出处理了~~~~
	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

}

它用于WebMvcConfigurationSupport配置的时候,会配置上这个ViewResolverComposite用于对所有的View解析器做聚合。

Demo:使用BeanNameViewResolver做一个自定义的视图

代码语言:javascript
复制
@Component
public class HelloView implements View {

    @Override
    public String getContentType() {
        return MediaType.TEXT_HTML_VALUE;
    }

    // 这里渲染,就向控制台写一句话即可~~~~
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
                       HttpServletResponse response) throws Exception {
        response.getWriter().print("Welcome to View:" + new Date());
    }
}

@Controller  
public class MyView {  
    @RequestMapping(value="/testBeanNameViewResolver")  
    public String testView(){  
        System.out.println("testBeanNameViewResolver");  
        return "helloView";
    }  
}

// 把BeanNameViewResolver 配置进容器
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        BeanNameViewResolver viewResolver = new BeanNameViewResolver();
        viewResolver.setOrder(10); // 这样能保证在InternalResourceViewResolver之前执行
        registry.viewResolver(viewResolver);
    }

    // 这是错误的注册方式~~~会让ViewResolverComposite可能失效~~
    //@Bean
    //public ViewResolver viewResolver() {
    //    BeanNameViewResolver viewResolver = new BeanNameViewResolver();
    //    viewResolver.setOrder(10); // 这样能保证在InternalResourceViewResolver之前执行
    //    return viewResolver;
    //}

这样我们访问:http://localhost:8080/demo_war_war/testBeanNameViewResolver就会自动到我们自定义的view上去。从现实页面:

备注:这个视图解析器的使用场景:一般用于自定义视图,然后通过这个视图解析器指过去

最后需要注意的是,这么多处理器,都实现了Order接口,因此自己向Spring MVC注册view解析器的时候,务必注意他们的顺序问题~~~(因为DispatcherServlet初始化的时候,会根据Order排序的)




Spring MVC默认装配的视图解析器们

开启注解:@EnableWebMvc。如下截图可以看到默认只会装配InternalResourceViewResolver这一个视图解析器,且是直接new InternalResourceViewResolver()的,都是默认值~~

不开启注解:@EnableWebMvc。默认装配的也是它(在DispatcherServlet.properties配置文件里)

由此可见默认情况下,它是支持jsp文件解析、访问的。若你想扩展一些别的视图解析,可以自己扩展注册~~

总结

Spring MVC很优秀的之一,就是把视图解析、渲染这块完全隔离了。同一份数据,若想改变暂展示的方式,只需要改配置即可,完全做到了模块化、可插拔化~

本篇讲解了几乎所有的解析器(除了ContentNegotiatingViewResolverResourceBundleViewResolver没讲,因为我认为可以把它单独抽取抽来讲解,因为还挺好玩的),然后我认为更重要的是了解View视图、渲染方面,而本文就做了一个非常好的铺垫作用~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年05月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • ViewResolver
    • AbstractCachingViewResolver 非常重要
      • UrlBasedViewResolver
    • BeanNameViewResolver
      • ViewResolverComposite
        • Spring MVC默认装配的视图解析器们
        • 总结
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档