• SpringMVC源码情操陶冶-View视图渲染


    本节简单分析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对象中
    判断条件为:

    1. exposeRequestAttributes设置为true,表明将request中的Attributes属性添加到model中,这样前端可以直接引用request对象中的attribute属性。其默认为false
    2. exposeSessionAttributes设置为true,表明将session中的Attributes属性添加到model中,这样前端可以直接引用session对象中的attribute属性。其默认为false
    3. 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(),下面将从这两部分进行简单的展开


    1.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:搜索


    2.FreeMarker#process()

    1. 此处主要是通过获取到的Template对象,对其持有的实际资源进行读取渲染后再重新写入以完成${}这样字符的含义

    2. model中所有的数据都会保留到request的Attributes对象中,所以FreeMarker可直接通过${attribute}获取相应的参数值

    小结

    1. ViewResolver帮助我们设置好对应的视图解析器比如FreeMarkerView,包含加载资源的prefix/suffix,以及是否将request/session对象属性绑定到model对象中

    2. 本文则以FreeMarkerView如何解析资源入手,简单的分析其中的操作逻辑,具体的细节读者可自行查询

  • 相关阅读:
    mailing list的原理
    关于结构体的使用
    c++ template
    IDA逆向
    重定向 301 302
    linux信号
    cmake编译选项
    mongodb超时
    普通java工程的resources目录寻址
    Vue基础---->VueJS的使用(二)
  • 原文地址:https://www.cnblogs.com/question-sky/p/7214798.html
Copyright © 2020-2023  润新知