本文源码基于SpringMVC 5.2.7
视图渲染是SpringMVC框架的重要一环,基本也就是最后一环了。请求经过前面一系列的处理最终是返回ModelAndView对象给到视图渲染模块。视图渲染模块则将其转换为前端可识别的html文本并写入response。并不是所有请求最终都会走到视图渲染模块,如果请求在前面的处理中已经将数据写到response,其实就没有必要经过视图渲染模块处理。这时只需要返回的恶ModelAndView是空对象(null),DispatcherServlet就不会走视图渲染逻辑。例如,注解@Responsebody的方法就不会走视图渲染逻辑,这种方法在返回处理器中就已经将数据写到response中了。
视图渲染模块包括两个部分:视图解析器和视图渲染。视图解析器用来将视图name转换为视图对象(org.springframework.web.servlet.View),很多请求处理完后返回的并使视图对象,而是字符串(代表视图name),所以首先需要将视图name转换为视图对象。视图渲染则是调用视图对象的render方法(View#render)。
视图解析器(ViewResolver)
视图解析器都是实现了接口ViewResolver,接口ViewResolver只定义了接口方法resolveViewName。
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
DispatcherServlet装配ViewResolver
跟HandlerMapping、HandlerAdapter这些组件一样,ViewResolver也是在系统初始化的时候装配到DispatcherServlet
- 如果"detectAllViewResolvers"打开,则从IOC容器中获取所有类型为ViewResolver的实例;否则进入2
- 从IOC容器中获取name为"viewResolver"的实例;
- 如果步骤1、2之后已经有viewResolver则装配过程结束,否则进入4;
- 通过DispatcherServlet默认装配策略中创建viewResolver实例,并装配给DispatcherServlet,装配过程结束。
默认情况“detectAllViewResolvers”是打开的,也就是说默认情况是从IOC容器中找到所有ViewResolver的Bean。
DispatcherServlet默认装配策略在《抽丝剥茧MVC之RequestMappingHandlerMapping》中有介绍,这里就不赘述。默认策略装配的视图处理器有1个
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
DispatcherServlet视图解析处理
DispatcherServlet初始化的视图解析可能有多个,在其解析视图的时候依次调用视图解析器的解析流程(ViewResolver#resolveViewName),如果返回的视图对象非空(null),则终止解析。
public class DispatcherServlet extends FrameworkServlet {
... ...
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 笔者注: 依次遍历视图解析器执行解析逻辑
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
... ...
}
常见的视图解析器
BeanNameViewResolver
BeanNameViewResolver是将视图name当作Bean name判断Spring IOC容器是否包含对应name的Bean,且Bean对象是View类型则认为解析出视图对象。
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
... ...
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = obtainApplicationContext();
//笔者注:判断容器中是否有viewName对应的Bean
if (!context.containsBean(viewName)) {
return null;
}
//笔者注:判断viewName对应的Bean对象是否View类型
if (!context.isTypeMatch(viewName, View.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
return null;
}
return context.getBean(viewName, View.class);
}
... ...
XmlViewResolver
XmlViewResolver主要用来从指定xml中加载View的Bean定义,再从中找到视图name对应的视图对象。所以XmlViewResolver先根据指定xml创建BeanFactory(默认的xml是"/WEB-INF/views.xml")然后从BeanFacotry中找到视图name对应的Bean对象。
public class XmlViewResolver extends AbstractCachingViewResolver
implements Ordered, InitializingBean, DisposableBean {
... ...
@Override
protected View loadView(String viewName, Locale locale) throws BeansException {
//笔者注:初始化BeanFactory
BeanFactory factory = initFactory();
try {
//笔者注:从BeanFactory找到对应Bean对象
return factory.getBean(viewName, View.class);
}
catch (NoSuchBeanDefinitionException ex) {
// Allow for ViewResolver chaining...
return null;
}
}
protected synchronized BeanFactory initFactory() throws BeansException {
if (this.cachedFactory != null) {
return this.cachedFactory;
}
ApplicationContext applicationContext = obtainApplicationContext();
Resource actualLocation = this.location;
if (actualLocation == null) {
//笔者注:默认的xml是"/WEB-INF/views.xml"
actualLocation = applicationContext.getResource(DEFAULT_LOCATION);
}
// Create child ApplicationContext for views.
GenericWebApplicationContext factory = new GenericWebApplicationContext();
factory.setParent(applicationContext);
factory.setServletContext(getServletContext());
// Load XML resource with context-aware entity resolver.
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.setEnvironment(applicationContext.getEnvironment());
reader.setEntityResolver(new ResourceEntityResolver(applicationContext));
reader.loadBeanDefinitions(actualLocation);
//笔者注:启动BeanFactory
factory.refresh();
if (isCache()) {
this.cachedFactory = factory;
}
return factory;
}
... ...
}
FreeMarkerViewResolver
FreeMarkerViewResolver能够解析出FreeMarkerView类型的视图对象。FreeMarkerView是基于FreeMarker模版引擎的视图对象。FreeMarkerViewResolver类结构如下。依次分析FreeMarkerViewResolver各个父类的职责。

AbstractCachingViewResolver
AbstractCachingViewResolver提供了缓存视图对象的功能,先取缓存,缓存有则返回,否则创建视图对象。
- cacheLimit:int类型,缓存的size,cacheLimit为0表示不开启缓存
- viewAccessCache:ConcurrentHashMap类型,视图对象访问时的缓存,与viewCreationCache保持同步,不会加全局锁,提高访问时的效率。
- viewCreationCache:LinkedHashMap类型,视图对象创建时的缓存,与viewAccessCache保持同步,viewAccessCache没有缓存时就会创建视图对象,创建的时候需要加全局锁。当size大于cacheLimit时删除比较旧的视图对象,同时删除viewAccessCache中对应的对象。
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
... ...
public View resolveViewName(String viewName, Locale locale) throws Exception {
//笔者注:没开启缓存(cacheLimit<=0),就直接创建视图对象
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
//笔者注:从viewAccessCache获取视图对象,没有全局锁,效率比较高
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
//笔者注:如果viewAccessCache没有缓存的对象,则需要加锁创建对象,因为并发可能其它线程刚刚创建好,所以先取一次
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
//笔者注:创建视图
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
//笔者注: 添加到缓存中
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
... ...
}
UrlBasedViewResolver
UrlBasedViewResolver提供了对重定向("redirect:")和转发("forward:")的处理。如果view name以"redirect:"开头则创建RedirectView类型对象;如果"forward:"开头则创建InternalResourceView类型对象。通过重写AbstractCachingViewResolver#createView实现
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
... ...
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
//笔者注:以redirect:开头创建RedirectView视图
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);
}
//笔者注:以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);
}
... ...
}
UrlBasedViewResolver还提供了根据用户设置的视图类型创建视图对象的功能,通过重写AbstractCachingViewResolver#loadView实现。
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
.. ...
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
//笔者注:创建视图对象
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//笔者注:视图类型由开发者设置
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
//笔者注:根据视图类型实例化对象
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setUrl(getPrefix() + viewName + getSuffix());
view.setAttributesMap(getAttributesMap());
//笔者注:设置相关属性 contentType
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
String requestContextAttribute = getRequestContextAttribute();
if (requestContextAttribute != null) {
view.setRequestContextAttribute(requestContextAttribute);
}
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;
}
... ...
}
AbstractTemplateViewResolver
AbstractTemplateViewResolver提供了一下视图类型的配置,通过重写UrlBasedViewResolver#buildView实现
- exposeRequestAttributes:bool类型,是否暴露请求属性,默认false
- allowRequestOverride:bool类型,是否允许HttpServletRequest属性被隐藏,如果model中有相同name的属性,默认false
- exposeSessionAttributes:bool类型,是否暴露会话属性,默认false
- allowSessionOverride:bool类型,是否允许HttpServletRequest会话属性被隐藏,如果model中有相同name的属性,默认false
- exposeSpringMacroHelpers:bool类型,是否暴露MacroHelpers,默认true
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {
... ...
@Override
//笔者注:重写父类的buildView
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;
}
... ...
}
FreeMarkerViewResolver
FreeMarkerViewResolver设置视图类型为FreeMarkerView.class
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
... ...
//笔者注:返回视图对象的类型
protected Class<?> requiredViewClass() {
return FreeMarkerView.class;
}
... ...
}
视图渲染
SpringMVC的视图类都是View的实现类,主要有View#render方法实现渲染。渲染的目的是将请求处理生成的model与视图结合返回给前端,或html文本或json数据等等。常见的视图类型有FreeMarkerView、MappingJackson2JsonView等。FreeMarkerView是基于FreeMarker模版引擎的一种视图,它是将model填充到模版中以html文本返回给客户端。MappingJackson2JsonView则是将model转换为json数据以application/json形式返回给客户端。下面主要分析一下FreeMarkerView的过程
FreeMarkerView
FreeMarkerView的类继承结构如下

AbstractView
AbstractView是FreeMarkerView视图渲染的入口,合并所有来源的数据,调用渲染接口。合并所有来源的数据包括:
- 开发者配置的属性
- 请求的路径变量
- 请求处理生成的model
- RequestContext,如果开启了"暴露RequestContext"的开关
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
... ...
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
//笔者注:合并数据model
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//笔者注:用合并后的model,渲染
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
/**
* Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.
* Dynamic values take precedence over static attributes.
*/
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
HttpServletRequest request, HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<>(size);
//笔者注:开发者配置的属性
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
//笔者注:请求的路径变量
mergedModel.putAll(pathVars);
}
if (model != null) {
//笔者注:请求处理生成的model
mergedModel.putAll(model);
}
// Expose RequestContext?
//笔者注:RequestContext对象
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;
}
... ...
}
AbstractTemplateView
AbstractTemplateView主要是重写了AbstractView的renderMergedOutputModel方法。主要逻辑如下
- 如果开启了暴露请求属性(exposeRequestAttributes),则暴露请求属性。如果model中已经有同名的属性且开启了不允许覆盖请求属性(allowRequestOverride),则抛异常
- 如果开启了暴露会话属性(exposeSessionAttributes),则暴露会话属性。如果model中已经有同名的属性且开启了不允许覆盖会话属性(allowSessionOverride),则抛异常
- 如果开启了暴露Spring MacroHelpers(exposeSpringMacroHelpers),则创建创建一个RequestContext对象并暴露
public abstract class AbstractTemplateView extends AbstractUrlBasedView {
... ...
protected final void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//笔者注:暴露请求属性
if (this.exposeRequestAttributes) {
Map<String, Object> exposed = null;
for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {
String attribute = en.nextElement();
if (model.containsKey(attribute) && !this.allowRequestOverride) {
throw new ServletException("Cannot expose request attribute '" + attribute +
"' because of an existing model object of the same name");
}
Object attributeValue = request.getAttribute(attribute);
if (logger.isDebugEnabled()) {
exposed = exposed != null ? exposed : new LinkedHashMap<>();
exposed.put(attribute, attributeValue);
}
model.put(attribute, attributeValue);
}
if (logger.isTraceEnabled() && exposed != null) {
logger.trace("Exposed request attributes to model: " + exposed);
}
}
//笔者注:暴露会话属性
if (this.exposeSessionAttributes) {
HttpSession session = request.getSession(false);
if (session != null) {
Map<String, Object> exposed = null;
for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) {
String attribute = en.nextElement();
if (model.containsKey(attribute) && !this.allowSessionOverride) {
throw new ServletException("Cannot expose session attribute '" + attribute +
"' because of an existing model object of the same name");
}
Object attributeValue = session.getAttribute(attribute);
if (logger.isDebugEnabled()) {
exposed = exposed != null ? exposed : new LinkedHashMap<>();
exposed.put(attribute, attributeValue);
}
model.put(attribute, attributeValue);
}
if (logger.isTraceEnabled() && exposed != null) {
logger.trace("Exposed session attributes to model: " + exposed);
}
}
}
//笔者注:暴露MacroHelpers
if (this.exposeSpringMacroHelpers) {
if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
throw new ServletException(
"Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
"' because of an existing model object of the same name");
}
// Expose RequestContext instance for Spring macros.
model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
new RequestContext(request, response, getServletContext(), model));
}
applyContentType(response);
if (logger.isDebugEnabled()) {
logger.debug("Rendering [" + getUrl() + "]");
}
renderMergedTemplateModel(model, request, response);
}
... ...
}
FreeMarkerView
FreeMarkerView就是调用FreeMarker模版引擎执行模版渲染并将数据写到response。实现了AbstractTemplateView的renderMergedTemplateModel方法。这里有一个重点的地方是获取对应的模版。主要依赖开发者配置的freemarker.template.Configuration。大致的逻辑是是找到相应目录下的文件。freemarker.template.Configuration中配置文件的相对路径,而文件名则是"prefix + viewName + suffix"拼接而成。prefix和 suffix会在FreeMarkerViewResolver中配置,viewName则是请求处理返回的视图name。
public class FreeMarkerView extends AbstractTemplateView {
... ...
protected void renderMergedTemplateModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
exposeHelpers(model, request);
doRender(model, request, response);
}
... ...
}
目录 目录
上一篇 异常解析器HandlerExceptionResolver
再下一篇 HttpMessageConverter
本文详细介绍了SpringMVC中的视图解析器和视图渲染机制,包括BeanNameViewResolver、XmlViewResolver、FreeMarkerViewResolver的工作原理,以及视图渲染过程中的模型合并和模板引擎应用。

1万+

被折叠的 条评论
为什么被折叠?



