SpringMVC源码情操陶冶-View视图渲染
2017-07-20 22:13
435 查看
本节简单分析View视图对象的render方法
View接口
最重要的就是render()方法,具体源码如下/** * Render the view given the specified model. * <p>The first step will be preparing the request: In the JSP case, * this would mean setting model objects as request attributes. * The second step will be the actual rendering of the view, * for example including the JSP via a RequestDispatcher. * @param model Map with name Strings as keys and corresponding model * objects as values (Map can also be {@code null} in case of empty model) * @param request current HTTP request * @param response HTTP response we are building * @throws Exception if rendering failed */ void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
下面我们就此接口方法展开分析
AbstractView
直接看源码@Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //综合model、内部属性staticAttributes和request对象中的View.PATH_VARIABLES,都封装在同一个Map集合中 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //为response做准备,默认是针对download请求 prepareResponse(request, response); //真实处理render操作,供子类实现调用 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
AbstractTemplateView-AbstractView的子类
抽象模板视图,源码如下@Override protected final void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //判断是否暴露request属性给前端,是则将所有的request属性加到model中 if (this.exposeRequestAttributes) { for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) { String attribute = en.nextElement(); //当不允许request对象中的属性被覆盖且model存在相同key时,会抛异常 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); model.put(attribute, attributeValue); } } //等同request if (this.exposeSessionAttributes) { HttpSession session = request.getSession(false); if (session != 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()) { logger.debug("Exposing session attribute '" + attribute + "' with value [" + attributeValue + "] to model"); } model.put(attribute, attributeValue); } } } //设置SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE属性到model中 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)); } //设置返回给前端的内容类型,可在ViewResolver中设置contentType属性 applyContentType(response); //抽象方法供子类调用实现 renderMergedTemplateModel(model, request, response); }
由以上代码分析可得,此类主要是判断是否将request和session的对象中的所有属性添加到Map类型的model对象中
判断条件为:
exposeRequestAttributes设置为true,表明将request中的Attributes属性添加到model中,这样前端可以直接引用request对象中的attribute属性。其默认为false
exposeSessionAttributes设置为true,表明将session中的Attributes属性添加到model中,这样前端可以直接引用session对象中的attribute属性。其默认为false
allowRequestOverride/allowSessionOverride设置为true,表明是否允许request/session对象中的attribute属性覆盖model中的同key键。其默认为false,在出现上述情况则会抛出异常
FreeMakerView-AbstractTemplateView实现类
关于AbstractTemplateView的实现类有很多,本文则选取常用的FreemarkerView来进行简析
FreeMarkerView#initServletContext()-初始化方法
此方法在父类WebApplicationObjectSupport#initApplicationContext()中被调用
protected void initServletContext(ServletContext servletContext) throws BeansException { if (getConfiguration() != null) { this.taglibFactory = new TaglibFactory(servletContext); } else { //查询springmvc上下文中是否存在FreeMarkerConfig接口bean,其一般是通过FreeMarkerConfigurer来注册的 //如果不存在FreeMarkerConfig bean对象则会抛异常 FreeMarkerConfig config = autodetectConfiguration(); setConfiguration(config.getConfiguration()); this.taglibFactory = config.getTaglibFactory(); } //初始servlet对象,其实是创建ServletContextResourceLoader对象,帮助可以获取"/WEB-INF/"下的资源 GenericServlet servlet = new GenericServletAdapter(); try { servlet.init(new DelegatingServletConfig()); } catch (ServletException ex) { throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex); } this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper()); }
此处主要获取FreeMarkerConfig对象,其一般由FreeMarkerConfigurer对象生成,可指定加载资源的路径和设置输出的一些属性,且这是必须注册到springmvc的bean工厂的
FreeMarkerView#renderMergedTemplateModel()-渲染视图
源码奉上protected void renderMergedTemplateModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //默认为空,待实现 exposeHelpers(model, request); //启动渲染程序 doRender(model, request, response); }
FreeMarkerView#doRender()-渲染操作
源码奉上protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 这里其实是将model中的所有属性都注册到request对象中 exposeModelAsRequestAttributes(model, request); // Expose all standard FreeMarker hash models.将FreeMarker的属性放到一个Map中 SimpleHash fmModel = buildTemplateModel(model, request, response); //此处的getUrl()由ViewResolver来设定,为prefix+viewName+suffix if (logger.isDebugEnabled()) { logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'"); } // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); //此处的操作比较复杂,需要慢慢分析 processTemplate(getTemplate(locale), fmModel, response); }
上述的代码具体解析分为两部分:模板对象Template获取和Template对象的处理方法process(),下面将从这两部分进行简单的展开
FreeMarkerView#getTemplate()
获取模板对象
protected Template getTemplate(Locale locale) throws IOException { //templateName为prefix+viewName+suffix return getTemplate(getUrl(), locale); }
protected Template getTemplate(String name, Locale locale) throws IOException { //最终是通过FreeMarkerConfigurer的内部属性Configuration来获取模板对象 return (getEncoding() != null ? getConfiguration().getTemplate(name, locale, getEncoding()) : getConfiguration().getTemplate(name, locale)); }
此处限于FreeMaker相关类
Configuration获取Template模板对象的步骤过于冗长,这里作下总结,具体读者可自行去阅读源码分析
获取Template对象是由
TemplateCache来实现的,其是通过
prefix+viewName+suffix作为整个文件名去找寻,其中prefix参数值不能以
/为开头,这在FreeMarker/Velocity是约定的,而jsp页面引擎InternalView支持
/开头的prefix,这点需要区别开来
具体的获取资源文件是通过Configuration指定的templateLoader去加载实际的资源,一般此处的templateLoader为
SpringTemplateLoader,支持templateLoaderPath为
classpath:搜索
FreeMarker#process()
此处主要是通过获取到的Template对象,对其持有的实际资源进行读取渲染后再重新写入以完成
${}这样字符的含义
model中所有的数据都会保留到request的Attributes对象中,所以FreeMarker可直接通过${attribute}获取相应的参数值
小结
ViewResolver帮助我们设置好对应的视图解析器比如FreeMarkerView,包含加载资源的prefix/suffix,以及是否将request/session对象属性绑定到model对象中
本文则以FreeMarkerView如何解析资源入手,简单的分析其中的操作逻辑,具体的细节读者可自行查询
相关文章推荐
- SpringMVC源码情操陶冶-ViewResolver视图解析
- SpringMVC源码情操陶冶-AbstractUrlHandlerMapping
- springMVC源码分析--视图AbstractView和InternalResourceView(二)
- SpringMVC源码情操陶冶-DispatcherServlet类简析(一)
- SpringMVC源码情操陶冶-AnnotationDrivenBeanDefinitionParser注解解析器
- SpringMVC源码(六)Controller控制器4-View视图解析器
- springMVC源码分析--视图View(一)
- 数据传递--------博客-----------springMVC源码分析--RequestToViewNameTranslator请求到视图名称的转换
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
- SpringMVC源码情操陶冶-DispatcherServlet
- SpringMVC源码情操陶冶-FreeMarker之web配置
- springMVC源码分析--视图AbstractView和InternalResourceView(二)
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
- SpringMVC源码情操陶冶-RequestMappingHandlerAdapter适配器
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
- SpringMVC源码情操陶冶-AbstractHandlerMethodMapping
- springMVC源码解析--ViewResolver视图解析器执行(三)
- springMVC源码解析--ViewResolver视图解析器执行(三)
- SpringMVC源码情操陶冶-ResourcesBeanDefinitionParser静态资源解析器