本文主要是介绍【Spring】抽丝剥茧SpringMVC-视图解析及渲染ViewResolverView,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文源码基于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对应的Beanif (!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 AbstractCachingViewResolverimplements Ordered, InitializingBean, DisposableBean {
... ...@Overrideprotected View loadView(String viewName, Locale locale) throws BeansException {//笔者注:初始化BeanFactoryBeanFactory 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);//笔者注:启动BeanFactoryfactory.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());//笔者注:设置相关属性 contentTypeString 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//笔者注:重写父类的buildViewprotected 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));}//笔者注:合并数据modelMap<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) {//笔者注:请求处理生成的modelmergedModel.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);}}}//笔者注:暴露MacroHelpersif (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
下一篇 ContentNegotiationManager
再下一篇 HttpMessageConverter
这篇关于【Spring】抽丝剥茧SpringMVC-视图解析及渲染ViewResolverView的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!