第24章 近距离接触SpringMVC主要角色(四)

第24章 近距离接触SpringMVC主要角色

24.5 各司其职的View

org.springframework.web.servlet.View是SpringMVC中将原本可能存在于DispatcherServlet中的视图渲染逻辑得以剥离出来的关键组件。通过引入该策略抽象接口,我们可以极具灵活性地支持各种视图渲染技术。

org.springframework.web.servlet.View的按口定义如下:

public interface View {
    
    
  String getContentType();
  void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

各种View实现类主要的职责就是在render(..)方法中实现最终的视图渲染工作,但这些对DispatcherServlet来说是透明的,DispatcherServlet只是直接接触ViewResolver所返回的View接口,获得相应引用后把视图渲染工作转交给返回的View实例即可。至于该View实例是什么类型,具体如何完成工作,DispatcherServlet是无须关心的。不过,对于我们来说,了解各个View实现类的实现原理,有助于我们更好地理解整个框架是如何运作的,并且,如果现有的View不能够满足我们的需要,我们也可以自定义一个需要的View实现类。

24.5.1 View实现原理回顾

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

  • 厨师为了能够提供统一样式的蛋糕,会使用模子来制作,只要提供不同成分的面团,经过相同的模子压制,就能够获得统一样式却不同口味的蛋糕。厨师用的模子(可能木质也可能金属质地)是不是与我们提供的JSP文件相似?那不同成分的面团跟我们提供的不同的模型数据是否类似?

  • 篆刻后的方印,只要蘸上不同颜色的印泥就能印出同一式样但不同颜色的印章图案。方印就是模板,不同的印泥就是要表现的数据,是否可以这么理解呢?

实际上,不管是生活中还是视图渲染过程中,只要使用模板这种模式,它们的工作原理就是一条路子下来的,如图24-18所示。

image-20220710164501255

所以,只要能够理解当前视图渲染的实现与生活中这些使用模板的场景之间的共同之处,那么,余下的工作将不再神秘。一个View实现类所要做的,就是使用相应的技术API将模板和最终提供的模型数据合并到一起,最终输出结果页面给客户端,所以,不难想象对应不同视图技术的View实现是一个什么样子。

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

public class JspView implements View {
    
    
	private String jspTemplateFileLocation;
  
	public String getContentType() {
    
    
		return "text/html;charset=UTF-8";
  	}
  
	public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
	response.setContentType(getContentType());
	exposeModelToRequest(model, request);
	request.getRequestDispatcher(jspTemplateFileLocation).forward(request,response);
  	}
  
	protected void exposeModelToRequest(Map model,HttpServletRequest request) {
    
    
		if(!model.isEmpty()) {
    
    
			Iterator<Map.Entry> iter = model.entrySet().iterator();
			while(iter.hasNext()) {
    
    
				Map.Entry entry = iter.next();
				String attrName = (String)entry.getKey();
				Object attrValue = entry.getValue();
				request.setAttribute(attrName, attrValue);
      		}
    	}
  	}
	// getter和setter方法定义.....
}

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

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

public class VelocityView implements View {
    
    
	private String vmTemplatelocation;
	private VelocityEngine engine;
  
	public String getContentType() {
    
    
		return "text/html;charset=UTF-8";
  	}
  
	public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		response.setContentType(getContentType());
		Context ctx = new VelocityContext();
	  	copyMapToContext(model, ctx);
		engine.mergeTemplate(vmTemplateLocation, ctx, response.getWriter());
  	}
  
	protected static void copyMapToContext(Map source, Context ctx) {
    
    
		Iterator iter = source.keySet().iterator();
		while(iter.hasNext()) {
    
    
			String key = (String)iter.next();
			Object value = source.get(key);
			ctx.put(key, value);
    	}
  	}
	//getter和setter方法定义
}

如果我们要使用Excel作为输出对象,那么我们的View实现类可能如下方代码清单所示。

public class ExcelView implements View {
    
    
	private String xlsTemplateLocation;
  
	public String getContentType() {
    
    
		return "application/vnd.ms-excel";
  	}
  
	public void render(Map model, HttpServletRequest request, HttpServ1etResponse response) throws Exception {
    
    
		response.setContentType(getContentType());
		//1.定位模板位置
		HSSFWorkbook workbook = readInExcelTemplate(x1sTemplateLocation);
		//2.合并数据和模板
		mergeModelwithTemplate(model, workbook);
		//3.输出到客户端
		ServletOutputStream out = response.getOutputStream();
		workbook.write(out);
		out.flush();
  	}
  
	private void mergeModelWithTemplate(Map model, HSSFWorkbook workbook) {
    
    
		workbook.getSheetAt(1).getRow(11).getCe1l((short)1).setCellValue((String)model.get("dataKey"));
		//...
  	}
  
	protected HSSFWorkbook readInExcelTemplate(String location) throws Exception {
    
    
		File xlsFile = new File(location);
		InputStream ins = new FileInputStream(xlsFile);
		POIFSFileSystem fs = new POIFSFileSystem(ins);
		HSSFWorkbook workbook = new HSSFWorkbook(fs);
		return workbook;
	}
	//getter和setter方法定义
}

怎么样?虽然只是原型代码,但已经足够说明问题了,不是吗?实际上,Spring MVC提供的针对各种视图技术的View实现也是按照同一条路子走下来的,只不过比我们的原型代码要严谨罢了。

24.5.2 可用的View实现类

SpringMVC提供的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方法更改这一默认值。

private String requestContextAttribute。 requestContextAttribute属性是要公开给视图模板使用的org.springframework.web.servlet.support.RequestContext对应的属性名,比如,如果setRequestContextAttribute("rc")的话,那么,相应的RequestContext实例将以rc作为键放入模型中。这样,我们就可以在视图模板中通过rc引用到该RequestContext。通常情况下,如果我们使用Spring提供的自定义标签,那么不需要公开相应的RequestContext。但如果不使用Spring提供的自定义标签,那么为了能够访问处理过程中所返回的错误信息等,就需要通过公开给视图模板的RequestContext来进行了。可以参考RequestContext的Javadoc文档了解它能够赋予我们的能力。

private final Map gtaticAttributes = new HashMap()。 如果视图有某些静态属性,比如页眉、页脚的固定信息等,只要将它们加入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}
...

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

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

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

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

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

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

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

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

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实现类,它们之间的关系如图24-19所示。

image-20220710170559590

InteralResourceView和JstlView都是面向单一JSP模板的View实现,二者的区别在于J2EE 1.4.之前的Web应用程序不支持JSTL。所以,这些Web应用程序只能使用InternalResourceView,而之后的Web应用程序因为支持JSTL,所以,使用JstlView是没有问题的。TilesView和TilesJstlView之间的区别与InteralResourceView和JstlView是类似的。不过,TilesView和TilesJstlView使用了Struts的Tiles视图技术,它们支持的是复合JSP视图。另外,Spring2.5之后也引入了对Tiles2(http://tiles.apache.org/)的支持,对应的TilesView实现位于org.springframework.web.servlet.view.tiles2包下面,与org.springframework.web.servlet.view.tiles包下面的Tiles1.x版本的TilesView和rilesJstlView相区别。

这些使用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应用程序要启用这两种技术渲染视图,那么,SpringMVC提供了FreeMarkerView和VelocityView两种View实现。因为二者都是基于同样的理念构建视图,所以,FreeMarkerView和VelocityView有着共同的父类AbstractTemplateView,它们之间的继承层次关系如图24.20所示。

image-20220710192042190

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 exposeSpringMacroHelpers = true。 是否需要为Spring提供的宏(macro)公开一个需要的RequestContext对象,默认需要,将以“springMacroRequestContext"为键公开一个RequestContext给合并过程。

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

FreeMarkerView和VelocityView的使用都有相应的ViewResolver支持,即FreeMarkerViewResolver和velocityViewResolver。不过,我们也可以在“面向多视图类型的ViewResolver”中使用它们。唯一需要注意的就是,使用这两种视图类型的时候,不要忘记通过FreeMarkerConfigurer和VelocityConfigurer为它们提供渲染过程中使用的模板引擎支持。

3. 面向二进制文档格式的View实现

该类的View实现主要指Excel和PDF形式的文档视图,通过设定合适的contentType,并且本地有相应的应用程序的话,这些文档将可以在浏览器中直接打开,而不是下载保存。 对于Excel形式的视图,SpringMVC提供了如下两个抽象类的视图实现。

  • AbstractExcelView。使用ApachePOI(http://poi.apache.org/)来构建Excel文档的View实现类,支持读入Excel模板文件,子类需要实现buildExcelDocument(..)模板方法,以给出具休的模型数掘到模板文件的合并逻辑。

  • AbstractJExcelView。该抽象类使用JExcelAPI(http://www.andykhan.com/jexcelapi/)作为视图的渲染API,同样支持现有Excel模板文件的读入,具体子类也需要通过实现buildExcelDocument(..)模板方法,来实现具体的模型数据到Excel模板文件的合并过程。

两种面向Excel的View实现类都支持按照Locale读入不同的Excel模板文件,读入顺序类似于:

(1)fileLocationzh_CN.xls;

(2)fileLocation_zh.xls;

(3)fileLocation.xls。

也就是说,我们可以为不同地区的用户提供不同的视图文件。

对应PDF形式的View实现类只有AbstractPdfView,它将使用iText来构建最终要输出的PDF文件。应该是API的限制,该类无法读入PDF形式的模板文件(当然,没有API的支持,也不可能做到)。我们只能通过该类创建新的PDF文件,然后将模型数据与要输入的格式一并纳入新创建的PDF文件对象中。该类也是抽象类,子类要实现buildPdfDocument(..)模板方法提供具体的输出逻辑。

因为面向二进制文档格式的View实现没有一个统一的模板形式,所以,Spring MVC无法提供通用的View实现类,只能在抽象父类中提供部分共同逻辑的实现,而具体的模型数据如何融入视图的显示逻辑,则需要子类在相应的模板方法中给出。

有关面向二进制文档格式的View实现的使用,我们可能需要使用“面向多视图类型的ViewResolver”,因为没有特定于二进制文档格式View实现的ViewResolver可用。

4. 面向JsperReport的View实现

面向JsperReport的View实现允许我们输出JasperRepor生成的相应格式的报表文件,包括HTML格式、CSV格式、Excel格式以及PDF格式。 只要我们在ModelAndView中将要合并到报表的数据返回,面向JsperReport的View实现将把这些数据按照指定格式输出到客户端。

面向JsperReport的View实现主要包括如下几个。

  • AbstractJasperReportssingleFormatView。 只负责输出单一类型的报表文件的View抽象类,实现了不同模板类型的读入以及数据的合并操作,将不同报表格式的输出通过模板方法下发给具体的子类实现,包括:

    • JasperReportsCsvView

    • JasperReportsHtmlView

    • JasperReportsPdfView

    • JasperReportsXlsView

    如果只需要根据模型数据输出单一文档格式的报表视图,选择以上对应的View子类即可。

  • JasperReportsMultiFormatView。 允许根据ModelAndView中的某个模型数据的值来决定输出何种格式的报表文档,默认使用“format”作为键。当然,我们可以通过setFormatKey(String)来更改这一默认键的名称。如果在ModelAndView中添加如下数据,并且使用JasperReportsMultiFormatView作为将要使用的View实现:

    ModelAndView mav = new ModelAnaView(...);
    mav.addObject("format","pdf");
    ...
    return mav;
    

    那么,JasperReportsMultiFormatView最终将通过JasperReportsPdfView输出PDF格式的报表文档。关于format的值与具体View实现类之间的关系,如表24-2所示。

    image-20220710193510466

当然,我们可以通过setFormatMappings(Properties)方法更改这一默认映射行为。

至于面向JsperReport的View实现类的使用,我们既可以使用特定的JasperReportsViewResolver来映射逻辑视图名到具体View实现类,也可以使用ResourceBundleViewResolver之类“面向多视图类型的ViewResolver”。更多的信息可以参考Spring的参考文档,其中对各种视图的应用有详细的介绍,但对于JasperReport相关的View实现,主要设定的属性可能只有viewClass、url和reportDataKey,如下所示:

reportView.class=org.springframework.Web.servlet.view.jasperreports.JasperReportsPafView
reportView.url=/WEB-INF/reports/jasperReportTemplate.jasper
reportView.reportDataKey=customJrDataSource

其中,reportDataKey作为ModelAndView中JasperReport需要的数据源(JRDataSource)的键,通常是必须的。

5. 使用XSLT技术的View实现

我想或许Cocoon(http://cocoon.apache.org/)才是使用XSLT技术实现视图的典型应用吧。不过,如果我们的应用程序也想以XSLT的形式处理数据的显示,SpringMVC也为我们提供了相应的View实现。要在基于SpringMVC的Web应用程序中使用XSLT处理数据到视图的显示,有如下两种View实现供选择。

AbstractxsltView。 使用AbstractxsltView进行XSLT数据转换,需要我们实现一个AbstractXsltView的子类,在该子类中覆盖模板方法SourcecreateXsltSource(..),在该模板方法中将Model中的数据转换为javax.xml.transform.Source类型返回,如下方代码清单所示。

public class SimpleXsltView extends AbstractXsltView {
    
    
	@Override
	protected Source createXsltSource(Map model, String root, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		Element rootElement = document.createElement(root);
		String author = (String)model.get("Author");
		Element authorNode = document.createElement("author");
		authorNode.setTextContent(author);
		rootElement.appenaChild(authorNode);
		return newDOMSource(rootElement);
  }
}

然后只需要配置该视图给相应的ViewResolver即可,比如:

simpleXslt.class=..SimpleXsltView
simp1eXslt.stylesheetLocation=/WEB-INF/xs1/simpleStyleSheet.xslt

XsltView。 使用XsltView要比AbstractXsltView省却不少麻烦,起码我们不用重新实现一个子类,我们只需要在ModelAndView中返回指定键标志的javax.xml.transform.Source实例即可,如下所示:

ModelAndView mav = new ModelAndView(..);
Sourcesource=...;
mav.addobject("xmlSource", source);
return mav;

其中“xmlSource”是xsltView寻找数据源时候要使用的键,需要在配置视图的时候指定:

xsltViewDemo.class=org.springframework.Web.serv1et.view.xslt.XsltView
xs1tViewDemo.url=/WEB-INF/xs1/simpleStyleSheet.xslt
xsltViewDemo.sourceKey=xmlSource

实际上,使用这两种view的主要工作应该在于XSLT式样表文件的编写,至于View的使用,只不过是配置一下罢了。不过,不管怎么样,除非你的应用程序之前有一致的要求,使用XSLT终归会强制你提供特定类型的数据,慎用。

6. RedirectView和逻辑视图名前缀

org.apringframework.web.servlet.view.RedirectView所做的工作,与以上所有提到的View实现类有点儿“大不同啊大不同”,就跟它的名字所展示的那样,Redirectview会对指定的Url做重定向操作。

如果设置RedirectView的http10Compatible属性为true,RedirectView将直接通过HttpServletReponse的sendRedirect(..)方法进行重定向操作,否则通过设置HTTP状态码(303)以及HTTPHeader(“Location”)达到同样的目的。不过在此之前,RedirectView会将ModelAndView中的模型数据附加到指定的URL后部,然后对URL进行编码。

使用RedirectView最多的地方是在Controller内,当然,通过相应的ViewResolver指定也是可以的,例如:

ModelAndView mav = new ModelAndView();
RedirectView view = new RedirectView("addUser.ao");
mav.setView(view);
return mav;

不过,不管是在Controller内直接实例化RedirectView使用,还是在相应的ViewResolver中配置,看起来都不是很简洁的样子。所以,SpringMVC还提供了另外一种进行请求重定向的方法,那就是在逻辑视图名中使用redirect或者forword前缀。

实际上,我们在前面已经接触过这两个字符前缀的使用了。如果在刚才的代码中使用结合redirect前缀的逻辑视图名,代码看起来就是如下所示:

ModelAndView mav = new ModelAndView("redirect:addUser.do");
return mav;

当然,我们可能更愿意将逻辑视图名以注入的方式取得,这在最初的实例中我们已经领教过了。

24.5.3 自定义View实现

虽然SpringMVC框架已经提供了足够多的View实现类支持,但有些情况下依然无法满足我们的应用要求。不过,好在框架自身对视图渲染相关关注点的分离,使得扩展并添加自定义View实现类也不是什么难事。

我们要添加的自定义View实现类所处的场景是这样的:FX系统中的交易等各类信息需要以报表的形式提供给相应顾客以及后台管理员,所以,在用户前台画面和后台管理画面上定义有相应的链接。点击这些链接之后,需要返回相应的PDF或者CSV格式的报表文件进行显示。 需求实际上很简单,不过Spring MVC提供的JasperReport相关的View实现类,以及面向二进制文档之类的view实现类(包括AbstractpdfView、AbstractExcelview等),都属于那种根据Web请求实时生成报表文件并输出的逻辑范畴。对于某些系统来说,这样的处理是合适的,但FX系统的报表根据法律要求,需要保存3~5年的时间。这些报表将是法律依据,每个顾客的信息,每笔数据的信息都不能遗漏。所以,在FX系统中,这些报表是通过批处理(Batch)在某一个时段全部输出的,输出后的信息保持于数据库中。当前台画面或者后台画面请求相应报表文件的时候,只需要根据Web请求信息到数据库中获取具体的文件名,并将对应的文件输出到客户端即可。自定义的View实现类所要做的,只是将已经通过批处理输出的PDF或者CSV报表文件传输给客户端显示。

我们可以继承AbstractView类。不过,既然同样需要根据URL去获取文件,直接继承AbstractUrlBasedView或许更合适一些。当然,这完全视具体情况而定。下方代码清单是我们完成的自定义View实现类StaticPdfView的代码演示。

public class StaticPafView extends AbstractUrlBasedView {
    
    
	public StaticPdfView() {
    
    
		setContentType("application/pdf");
  }
  
	@Override
	protected void renderMergeaOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		response.setContentType(getContentType());
		InputStream ins = new FileInputStream(getUrl());
		OutputStream out = response.getOutputStream();
		IOUtils.copy(ins, out);
		out.flush();
		IOUtils.closeQuietly(ins);
		IOUtils.closeQuietly(out);
  }
}

十分简单,不是吗?只是根据url信息读取文件,然后通过response输出到客户端即可。而View实现类里面所要做的不就是这些吗?根据具体场景构建视图内容,然后通过HttpServletResponse的Writer或者OutputStream,将构建后的视图内容输出到客户端。就这点儿工作!

因为通常的ViewResolver实现都继承了AbstractViewResolver的默认开启缓存功能,所以,通过ViewResolver来查找并使用我们的StaticPdfView并非合适的方式。另外,当前场景中,StaticPdfView所需要的url信息是从数据库获取的,显然无法通过相应ViewResolver进行定义(当然,不排除其他场景下结合ViewResolver使用StaticPdfView的情形)。

鉴于以上两点,在相应的Controller内部直接构造StaticPafView的实例并返回,或许是比较合适的做法。也就是说,在Controller中是直接返回view实例还是返回逻辑视图名,需要根据情况权衡。在大多数情况下,推荐使用返回逻辑视图名的做法,但不排除直接返回View实例的情况。StaticPdfUrlviewController提供了一段代码示例,演示了StaticPdfView的使用,如下方代码清单所示。

public class StaticPdfUrlViewController extends AbstractController {
    
    
	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		ModelAndView mav = new ModelAndView();
		View view = constructView(request);
		mav.setView(view);
		mav.addObject(....);//如果必要,添加其他模型数据
		return mav;
  	}

  	private View constructView(HttpServletRequest request) {
    
    
		String pdfFilePath = getPdfFileLocation(request);
		StaticPdfView view = new StaticPdfView();
		view.setUrl(pdfFilePath);
		return view;
  	}

  	protected String getPdfFileLocation(HttpServletRequest request) {
    
    
		//根据请求参数从数据库中取得相应的ur1信息
		String url = ...;
		return url;
  	}
}

方法getPdfFileLocation(..)将抽取Web请求参数,然后根据这些参数查询数据库,并返回对应的PDF文件路径。具体实现可能随前后台之间的参数约定而有少许差异。

注意:通常,自定义View实现类需要结合相应的ViewResolver才能使用,直接在Controller中实例化View并非大部分情况下的做法。对某类View来说,完全可以为其单独声明一个ViewResolver,指定合适的优先级别(通过order属性)。即使现用的ViewResolver无法满足需要,为某类View实现类提供自定义的ViewResolver实现类也并非难事。各位不妨考虑下,类似StaticPdfView这样的View实现,除了在Controller中直接实例化,是否可以为其提供一个自定义的ViewResolver呢?现在你是否准备自定义自己的View实现类了呢?

24.6 小结

HandlerMapping、Controller、ModelAndView、ViewResolver和View可以算是Spring MVC框架中的“五虎将”,它们共同组成了SpringMVC框架的强大躯干。本章对它们进行了详细的介绍,希望你完成本章内容之后,对它们已经了然于心了。不过,这五个角色并非SpringMVC的全部,没有了其他角色的支持,Spring MVC也不会看起来这么饱满。接下来,我们将一起看一下Spring MVC家族中的其他成员。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125771376