Spring MVC各组件近距离接触--下下--05

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC各组件近距离接触--下下--05相关的知识,希望对你有一定的参考价值。

Spring MVC各组件近距离接触--下下--05


各司其职的View

org.springframework.web.serviet.view是SpringMVC中将原本可能存在于Dispatcherservlet中的视图渲染逻辑得以剥离出来的关键组件。

通过引入该策略抽象接口, 我们可以极具灵活servlet中的视图渲染逻辑得以剥离出来的关键组件。通过引入该策略抽象接口,我们可以极具灵活性地支持各种视图渲染技术。

public interface View 
    //三个常量属性省略 
    ...
	
	String getContentType();

	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;


各种view实现类主要的职责就是在render(⋯)方法中实现最终的视图渲染工作,但这些对Dispatcherservlet来说是透明的, Dispatcherservlet只是直接接触ViewResolver所返回的view接口, 获得相应引用后把视图渲染工作转交给返回的view实例即可。

至于该view实例是什么类型,具体如何完成工作, Dispatcherservlet是无须关心的。

不过,对于我们来说,了解各个view实现类的实现原理, 有助于我们更好地理解整个框架是如何运作的, 并且, 如果现有的view不能够满足我们的需要, 我们也可以自定义一个需要的view实现类。


View实现原理回顾

总地来说,当前绝大多数的视图渲染技术都是构建在模板的原理之上。我们回想一下,这种基于模板视图生成方式在我们的生活中到处可见。

  • 厨师为了能够提供统一样式的蛋糕, 会使用模子来制作, 只要提供不同成分的面团,经过相同的模子压制,就能够获得统一样式却不同口味的蛋糕。厨师用的模子(可能木质也可能金属的模子压制)是不是与我们提供的JSP文件相似? 那不同成分的面团跟我们提供的不同的模型数据是否类似?
  • 篆刻后的方印,只要蘸上不同颜色的印泥就能印出同一式样但不同颜色的印章图案。方印就是模板,不同的印泥就是要表现的数据, 是否可以这么理解呢?

实际上,不管是生活中还是视图渲染过程中,只要使用模板这种模式,他们的工作原理就是一条路子下来的:


所以,只要能够理解当前视图渲染的实现与生活中这些使用模板的场景之间的共同之处, 那么,余下的工作将不再神秘。

一个view实现类所要做的,就是使用相应的技术API将模板和最终提供的模型数据合并到一起, 最终输出结果页面给客户端, 所以, 不难想象对应不同视图技术的view实现是一个什么样子。

如果我们要使用JSP文件作为模板输出视图页面,那么我们的View实现类可能如下面所示:

public class JspView implements View 

    private String jspTemplateFileLocation;

    @Override
    public String getContentType() 
        return "text/html;charset=UTF-8";
    

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception 
        response.setContentType(getContentType());
        exposeModelToRequest(model,request);
        request.getRequestDispatcher(jspTemplateFileLocation).forward(request,response);
    

    private void exposeModelToRequest(Map<String,?> model, HttpServletRequest request) 
         if(!model.isEmpty())
             Iterator<? extends Map.Entry<String, ?>> iter = model.entrySet().iterator();
             while(iter.hasNext())
                 Map.Entry<String, ?> entry = iter.next();
                 String name = entry.getKey();
                 Object value = entry.getValue();
                 request.setAttribute(name,value);
             
         
    

    public String getJspTemplateFileLocation() 
        return jspTemplateFileLocation;
    

    public void setJspTemplateFileLocation(String jspTemplateFileLocation) 
        this.jspTemplateFileLocation = jspTemplateFileLocation;
    

JSP模板文件与模型数据的合并(merge)操作将由Web容器(比如Tomcat)来完成,所以,这里我们只是通过Servlet API将合并的工作转发给Web容器即可。


如果我们使用Velocity模板输出视图页面,那么我们的View实现类可能如下所示:

public class VelocityView implements View 
    private String vmTemplateLocation;
    private VelocityEngine engine;

    @Override
    public String getContentType() 
        return "text/html;charset=UTF-8";
    


    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception 
          response.setContentType(getContentType());
          Context ctx=new VelocityContext();
          copyMapToContext(model,ctx);
          engine.mergeTemplate(vmTemplateLocation,ctx,response.getWriter());
    

    private void copyMapToContext(Map<String,?> model, Context ctx) 
        if(!model.isEmpty())
            Iterator<? extends Map.Entry<String, ?>> iter = model.entrySet().iterator();
            while(iter.hasNext())
                Map.Entry<String, ?> entry = iter.next();
                String name = entry.getKey();
                Object value = entry.getValue();
                ctx.put(name,value);
            
        
    


如果我们要使用Excel作为输出对象,那么我们的View实现类可能如下面所示:

public class ExcelView implements View 
    private String xlsTemplateLocation;

    @Override
    public String getContentType() 
        return "application/vnd.ms-excel";
    


    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception 
        response.setContentType(getContentType());
        //1.定位模板位置
        HSSFWorkBook workBook=readInExcelTemplate(xlsTemplateLocation);
       //2.合并数据和模板
        mergeModelWithTemplate(model,workBook);
       //3.输出到客户端
        ServletOutputStream out = response.getOutputStream();
        workBook.write(out);
        out.flush();
       

怎么样?虽然只是原型代码,但已经足够说明问题了,不是吗?

实际上,Spring MVC提供的针对各种视图技术的View实现也是按照同一条路子走下来的,只不过比我们的原型代码要严谨罢了。


可用的View实现类

Spring MVC提供的View实现类都直接或者间接继承自org.springframework.web.servlet. view.AbstractView

该类定义了大多数View实现类都需要的一些属性和简单的模板化的实现流程。

AbstractView为所有view子类定义的属性是如下几个。

	private String contentType = DEFAULT_CONTENT_TYPE;

DEFAULT_CONTENT_TYPE的内容是 “text/html;charset=IS0-8859-1”。我们可以通过contentType的setter方法更改这一默认值。

	public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";
	
	public void setContentType(String contentType) this.contentType = contentType; 

	private String requestContextAttribute;

requestContextAttribute属性是要公开给视图模板使用的org.springframework.web.servlet.support.RequestContext对应的属性名,比如,如果setRequestContextAttribute("rc")的话,那么,相应的RequestContext实例将以rc作为键放入模型中。

这样,我们就可以在视图模板中通过rc引用到该RequestContext。通常情况下,如果我们使用Spring提供的自定义标签,那么不需要公开相应的RequestContext

但如果不使用Spring提供的自定义标签,那么为了能够访问处理过程中所返回的错误信息等,就需要通过公开给视图模板的RequestContext来进行了。可以参考RequestContextJavadoc文档了解它能够赋予我们的能力。


	private final Map<String, Object> staticAttributes = new LinkedHashMap<String, Object>();

如果视图有某些静态属性,比如 页眉、页脚的固定信息等,只要将它们加入staticAttributes,那么,AbstractView将保证这些静 态属性将一并放入模型数据中,最终一起公开给视图模板。

既然所有的View实现子类都继承自AbstractView,那么它们也就都拥有了指定静态属性的能力。

比如我们在“面向多视图类型支持的ViewResolver”中定义视图映射的时候,为某些具体视图定义指定了静态属性,如下所示:

<bean name="viewTemplate" class="org.springframework.Web.servlet.view.InternalResourceView" abstract="true" p:attributesCSV="copyRight=spring21.cn,author= fujohnwang">
</bean>

那么,现在我们就可以像普通的模型数据那样,在视图模板中访问这些静态属性,如下所示:

...
Author: $author
<br/>
Copyright: $copyRight
...

不过,除了通过attriutesCSV属性以CSV字符串形式传入多个静态属性,我们还可以通过attributes属性以Properties的形式传入静态属性,或者通过attributeMap属性以Map的形式传入静态参数。

	public void setAttributesCSV(String propString) throws IllegalArgumentException 
       ...
       //核心是
       this.staticAttributes.put(name, value);
	
    
    
	public void setAttributes(Properties attributes) 
		CollectionUtils.mergePropertiesIntoMap(attributes, this.staticAttributes);
	
    
    public void setAttributesMap(Map<String, ?> attributes) 
		if (attributes != null) 
			for (Map.Entry<String, ?> entry : attributes.entrySet()) 
			//核心是: this.staticAttributes.put(name, value);
				addStaticAttribute(entry.getKey(), entry.getValue());
			
		
	

AbstractView除了定义了以上公共属性以外,还定义了一个简单的模板化的方法流程。

(1)将添加的静态属性全部导入到现有的模型数据Map中,以便后继流程在合并视图模板的时候可以获取这些数据。

(2)如果requestContextAttribute被设置(默认为null),则将其一并导入现有的模型数据 Map中:

(3)根据是否要产生下载内容,设置相应的HTTP Header。

(4)公开renderMergedOutputModel(..)模板方法给子类实现。

	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception 
		...
		//将相关属性全部导入模型数据Map中
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		//根据是否要产生下载内容,设置相应的HTTP Header
		prepareResponse(request, response);
		//公开renderMergedOutputModel(..)模板方法给子类实现
		renderMergedOutputModel(mergedModel, request, response);
	

createMergedOutputModel会将哪些属性放入模型数据的map集合中呢?

protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) 
        //判断是否要讲路径变量设置到模型map集合
		@SuppressWarnings("unchecked")
		Map<String, Object> pathVars = (this.exposePathVariables ?
				(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

		//将静态属性也都放入模型map
		int size = this.staticAttributes.size();
		size += (model != null ? model.size() : 0);
		size += (pathVars != null ? pathVars.size() : 0);

		Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
		mergedModel.putAll(this.staticAttributes);
		if (pathVars != null) 
			mergedModel.putAll(pathVars);
		
		if (model != null) 
			mergedModel.putAll(model);
		

		// 如果requestContextAttribute被设置(默认为null),则将其一并导入现有的模型数据 Map中:
		if (this.requestContextAttribute != null) 
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		

		return mergedModel;
	

prepareResponse是否要产生下载内容,设置相应的HTTP Header:

	protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) 
	    //例如如果需要渲染的是Excel或者pdf,那么返回的结果就会产生下载内容
		if (generatesDownloadContent()) 
			response.setHeader("Pragma", "private");
			response.setHeader("Cache-Control", "private, must-revalidate");
		
	

这样,AbstractView的直接或者间接子类,就可以在现有属性和流程的基础上进行开发了。


AbstractUrlBasedView

AbstractView中一个主要的扩展类是org.springframework.web.servlet.view.AbstractUrlBasedView,AbstractUrlBasedView为子类提供的公共设施很简单,只有一个String型的 ur1。那些需要根据模板路径读入模板文件的View实现,大都属于AbstractUrlBasedView门下。

public abstract class AbstractUrlBasedView extends AbstractView implements InitializingBean 
    //内部维护一个模板文件路径的URL
	private String url;
	...
	public void afterPropertiesSet() throws Exception 
		//判断是否强制要求有默认文件路径---这个判断方法就需要子类来决定了,父类默认返回true
		if (isUrlRequired() && getUrl() == null) 
			throw new IllegalArgumentException("Property 'url' is required");
		
	
	
	protected boolean isUrlRequired() 
		return true;
	
	
	//默认会去检查对应的模板文件资源是否存在--子类按需求覆盖
    public boolean checkResource(Locale locale) throws Exception 
		return true;
	

AbstractView和AbstractUrlBasedView是所有View实现类的“总统领”,那些不需要指定url的View实现类大都归于AbstractView门下,余下的则由AbstractUrlBasedView管辖。在这样的前提 下,我们再来看各种实际可用的view实现类。


1.使用JSP技术的view实现

属于该类别的View实现主要包括:

  • org.springframework.web.servlet.view.InternalResourceView
  • org.springframework.web.servlet.view.JstlView
  • org.springframework.web. servlet.view.tiles.TilesView
  • org. springframework.web.servlet.view.tiles.TilesJstlView

其中,org.springframework.web.servlet.view.InternalResourceView是面向JSP技术的主 要view实现类,它们之间的关系如图所示。


InteralResourceViewJstlView都是面向单一JSP模板的view实现,二者的区别在于J2EE 1.4 之前的Web应用程序不支持JSTL

所以,这些Web应用程序只能使用InternalResourceView,而之后的Web应用程序因为支持JSTL,所以,使用JstlView是没有问题的。

TilesView和TilesJstlView之间的区别与InteralResourceView和JstlView是类似的。

不过,TilesView和TilesJstlView 使用了Struts的Tiles视图技术,它们支持的是复合JSP视图。

另外,Spring 2.5之后也引入了对Tiles 2(http://tiles.apache.org/)的支持,对应的TilesView实现位于org.springframework.web.servlet. view.tiles2包下面,与org.springframework.web.servlet.view.tiles包下面的Tiles 1.x版本 的TilesView和TilesJstlView相区别。

这些使用JSP技术的View实现,虽然可以在"面向多视图类型的ViewResolver"的映射关系中单独配置,不过,因为它们有特定与自定的ViewResolver,即InternalResourceViewResolver,所以,更多时候,只需要在使用之前变换一下如下配置中具体的viewClass类型即可。

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
           <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
           <property name="prefix" value="/WEB-INF/jsp"/>
           <property name="suffix" value=".jsp"/>
    </bean>

不过,Tiles视图的使用与单纯的JSP视图在使用上存在一点儿差异,我们需要为TilesView和TilesJstlView的渲染提供必需的DefinitionsFactory。

这个工作可以通过TilesConfigurer类完 成,将TilesConfigurer添加到webApplicationContext之后,它将为容器内的TilesView和TilesJstlView的渲染提供绑定到ServletContext的DefinitionsFactory。

TilesConfigurer的配置如下所示:

<bean id="tilesConfigurer" class="org.springframework.Web.servlet.view.tiles.TilesConfigurer">    
  <property name="definitions">
    <list>
        <value>/WEB-INF/defs/tiles-def1.xml</value> 
        <value>/WEB-INF/defs/tiles-def2.xml</value> 
        ...
    </list>
  </property> 
</bean> 

2.使用通用模板技术的view实现

通用模板技术现在比较主流的是Velocity和Freemarker。

如果我们的Web应用程序要启用这两种技术渲染视图,那么,Spring MVC提供了FreeMarkerView和velocityView两种View实现。

因为二者都是基于同样的理念构建视图,所以,FreeMarkerView和velocityView有着共同的父类AbstractTemplateView,它们之间的继承层次关系如图所示。

AbstractTemplateView定义了几个boolean属性,让我们可以决定是否公开暴露某些数据给最终的合并过程,如下所述。

  • private boolean exposeRequestAttributes=false。是否需要将request中的所有属性公开给合并过程,默认为false。
  • private boolean allowRequestoverride = false。是否允许request中的属性覆盖ModelAndView中同名的attribute,默认不允许这么做。
  • private boolean exposesessionAttributes =false。是否要将session中的属性公开给视图模板与模型数据的合并过程,默认不做。
  • private boolean allowSessionoverride = false。是否允许session中同名的属性覆盖掉返回的ModelAndview中的属性,默认也是不允许这么做。
  • private boolean exposespringMaсroHelpers=true。是否需要为Spring提供的宏(macro)公开一个需要的RequestContext对象,默认需要,将以“springMacroRequestContext”为键公开一个RequestContext给合并过程。

除了这些,FreeMarkerView和VelocityView自身也定义了几个属性可以进一步限定视图渲染过程,比如velocityView允许我们通过dateToolAttribute和numberToolAttribute公开Velocity Tools(http://velocity.apache.org/tools/devel/)的DateTool和NumberTool给模板使用。

FreeMarkerView和velocityView的使用都有相应的ViewResolver支持,即FreeMarkerViewResolver和VelocityViewResolver。

不过,我们也可以在“面向多视图类型的ViewResolver”

以上是关于Spring MVC各组件近距离接触--下下--05的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC各组件近距离接触--下--04

近距离看GPU计算

近距离看GPU计算

如何用Nearby Service让你的游戏实现近距离联机

Spring框架进阶Spring V2.0 MVC

近距离看GPU计算