spring的view
标签: spring的view HarmonyOS博客 51CTO博客
2023-07-25 18:24:14 139浏览
http://book.51cto.com/art/200908/147512.htm
24.5.1 View实现原理回顾
总地来说,当前绝大多数的视图渲染技术都是构建在模板的原理上。我们回想一下,这种基于模板的视图生成方式在我们的生活中到处可见。
厨师为了能够提供统一样式的蛋糕,会使用模子来制作,只要提供不同成分的面团,经过相同的模子压制,就能够获得统一样式却不同口味的蛋糕。厨师用的模子(可能木质也可能金属质地)是不是与我们提供的JSP文件相似?那不同成分的面团跟我们提供的不同的模型数据是否类似?
篆刻后的方印,只要蘸上不同颜色的印泥就能印出同一式样但不同颜色的印章图案。方印就是模板,不同的印泥就是要表现的数据,是否可以这么理解呢?
实际上,不管是生活中还是视图渲染过程中,只要使用模板这种模式,它们的工作原理就是一条路子下来的,如图24-18所示。
(点击查看大图)图24-18 使用模板的工作原理 |
所以,只要能够理解当前视图渲染的实现与生活中这些使用模板的场景之间的共同之处,那么,余下的工作将不再神秘。一个View实现类所要做的,就是使用相应的技术API将模板和最终提供的模型数据合并到一起,最终输出结果页面给客户端,所以,不难想象对应不同视图技术的View实现是一个什么样子。
如果我们要使用JSP文件作为模板输出视图页面,那么我们的View实现类可能如代码清单24-29所示。
代码清单24-29 基于JSP的View实现类原型代码示例
1. public class JspView implements
2. private
3.
4. public
5. return "text/html;charset=UTF-8";
6. }
7.
8. public void
9. throws
10. response.setContentType(getContentType());
11. exposeModelToRequest(model,request);
12. request.getRequestDispatcher(jspTemplate
FileLocation).forward(request, response);
13. }
14.
15. protected void
16. {
17. if(!model.isEmpty())
18. {
19. Iterator<Map.Entry> iter = model.entrySet().iterator();
20. while(iter.hasNext())
21. {
22. Map.Entry entry = iter.next();
23. String attrName = (String)entry.getKey();
24. Object attrValue = entry.getValue();
25. request.setAttribute(attrName, attrValue);
26. }
27. }
28. }
29. // getter和setter方法定义……
30. }
JSP模板文件与模型数据的合并(merge)操作将由Web容器(比如Tomcat)来完成,所以,这里我们只是通过Servlet API将合并的工作转发给Web容器即可。
如果我们使用Velocity模板输出视图页面,那么我们的View实现类可能如代码清单24-30所示。
代码清单24-30 基于Velocity的View实现类原型代码示例
1. public class VelocityView implements
2. private
3. private
4.
5. public
6. return "text/html;charset=UTF-8";
7. }
8.
9. public void
10. throws
11. response.setContentType(getContentType());
12. new
13. copyMapToContext(model,ctx);
14. engine.mergeTemplate(vmTemplateLocation, ctx,
response.getWriter());
15. }
16.
17. protected static void
18. {
19. Iterator iter = source.keySet().iterator();
20. while(iter.hasNext())
21. {
22. String key = (String)iter.next();
23. Object value = source.get(key);
24. ctx.put(key, value);
25. }
26. }
27. // getter和setter方法定义
28. }
如果我们要使用Excel作为输出对象,那么我们的View实现类可能如代码清单24-31所示。
代码清单24-31 基于Excel的View实现类原型代码示例
1. public class ExcelView implements
2. private
3.
4. public
5. return "application/vnd.ms-excel";
6. }
7.
8. public void
9. throws
10. response.setContentType(getContentType());
11. // 1.定位模板位置
12. HSSFWorkbook workbook = readInExcelTemplate
(xlsTemplateLocation);
13. // 2.合并数据和模板
14. mergeModelWithTemplate(model,workbook);
15. // 3.输出到客户端
16. ServletOutputStream out = response.getOutputStream();
17. workbook.write(out);
18. out.flush();
19. }
20. private void
21. 1).getRow(11).getCell((short)1).
setCellValue((String)model.get("dataKey"));
22. // ...
23. }
24.
25. protected HSSFWorkbook readInExcelTemplate
(String location) throws
26. {
27. new
28. new
29. new
30. new
31. return
32. }
33. // getter和setter方法定义
34. }
怎么样?虽然只是原型代码,但已经足够说明问题了,不是吗?实际上,Spring MVC提供的针对各种视图技术的View实现也是按照同一条路子走下来的,只不过比我们的原型代码要严谨罢了。
24.5.2 可用的View实现类(1)
《Spring揭秘》第24章近距离接触Spring MVC主要角色,HandlerMapping、Controller、ModelAndView、ViewResolver和View可以算是Spring MVC框架中的"五虎将",它们共同组成了Spring MVC框架的强大躯干。本章对它们进行了详细的介绍,希望你完成本章内容之后,对它们已经了然于心了。本节为大家介绍可用的View实现类。
AD:
24.5.2 可用的View实现类(1)
Spring MVC提供的View实现类都直接或者间接继承自org.springframework.web.servlet. view.AbstractView。该类定义了大多数View实现类都需要的一些属性和简单的模板化的实现流程。
AbstractView为所有View子类定义的属性是如下几个。
private String contentType = DEFAULT_CONTENT_TYPE。DEFAULT_CONTENT_TYPE的内容是"text/html;charset=ISO-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 staticAttributes = new HashMap()。如果视图有某些静态属性,比如页眉、页脚的固定信息等,只要将它们加入staticAttributes,那么,AbstractView将保证这些静态属性将一并放入模型数据中,最终一起公开给视图模板。既然所有的View实现子类都继承自AbstractView,那么它们也就都拥有了指定静态属性的能力。比如我们在"面向多视图类型支持的ViewResolver"中定义视图映射的时候,为某些具体视图定义指定了静态属性,如下所示:
1. <bean name="viewTemplate"
2. class="org.springframework.Web.servlet.
view.InternalResourceView"
3. abstract="true"
4. p:attributesCSV="copyRight= spring21.cn ,
author= fujohnwang ">
5. </bean>
那么,现在我们就可以像普通的模型数据那样,在视图模板中访问这些静态属性,如下所示:
1. ...
2. Author: ${author}
3. <br/>
4. Copyright: ${copyRight}
5. ...
不过,除了通过attributesCSV属性以CSV字符串形式传入多个静态属性,我们还可以通过attributes属性以Properties的形式传入静态属性,或者通过attributesMap属性以Map的形式传入静态参数。
AbstractView除了定义了以上公共属性以外,还定义了一个简单的模板化的方法流程。
(1) 将添加的静态属性全部导入到现有的模型数据Map中,以便后继流程在合并视图模板的时候可以获取这些数据。
(2) 如果requestContextAttribute被设置(默认为null),则将其一并导入现有的模型数据Map中;
(3) 根据是否要产生下载内容,设置相应的HTTP Header。
(4) 公开renderMergedOutputModel(..)模板方法给子类实现。
这样,AbstractView的直接或者间接子类,就可以在现有属性和流程的基础上进行开发了。
AbstractView中一个主要的扩展类是org.springframework.web.servlet.view.Abs- tractUrlBasedView,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所示。
(点击查看大图)图24-19 使用JSP的View实现类 |
InteralResourceView和JstlView都是面向单一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类型即可:
1. <bean id="viewResolver"
2. class="org.springframework.Web.servlet.view.
InternalResourceViewResolver">
3. "viewClass" value="org.
springframework.Web.servlet.view.JstlView"/>
4. "prefix" value="/WEB-INF/jsp/"/>
5. "suffix" value=".jsp"/>
6. </bean>
不过,Tiles视图的使用与单纯的JSP视图在使用上存在一点儿差异,我们需要为TilesView和TilesJstlView的渲染提供必需的DefinitionsFactory。这个工作可以通过TilesConfigurer类完成,将TilesConfigurer添加到WebApplicationContext之后,它将为容器内的TilesView和TilesJstlView的渲染提供绑定到ServletContext的DefinitionsFactory。TilesConfigurer的配置如下所示:
1. <bean id="tilesConfigurer"
2. class="org.springframework.Web.servlet.view.tiles.TilesConfigurer">
3. "definitions">
4. <list>
5. <value>/WEB-INF/defs/tiles-def1.xml</value>
6. <value>/WEB-INF/defs/tiles-def2.xml</value>
7. ...
8. </list>
9. </property>
24.5.2 可用的View实现类(2)
《Spring揭秘》第24章近距离接触Spring MVC主要角色,HandlerMapping、Controller、ModelAndView、ViewResolver和View可以算是Spring MVC框架中的"五虎将",它们共同组成了Spring MVC框架的强大躯干。本章对它们进行了详细的介绍,希望你完成本章内容之后,对它们已经了然于心了。本节为大家介绍可用的View实现类。
AD:
24.5.2 可用的View实现类(2)
2. 使用通用模板技术的View实现
通用模板技术现在比较主流的是Velocity和Freemarker。如果我们的Web应用程序要启用这两种技术渲染视图,那么,Spring MVC提供了FreeMarkerView和VelocityView两种View实现。
因为二者都是基于同样的理念构建视图,所以,FreeMarkerView和VelocityView有着共同的父类AbstractTemplateView,它们之间的继承层次关系如图24-20所示。
图24-20 使用通用模板技术的View实现 |
AbstractTemplateView定义了几个boolean属性,让我们可以决定是否公开暴露某些数据给最终的合并过程,如下所述。
private boolean exposeRequestAttributes = false。是否需要将request中的所有属性公开给合并过程,默认为false。
private boolean allowRequestOverride = false。是否允许request中的属性覆盖Model- AndView中同名的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公开Velocity Tools(http:// velocity.apache.org/tools/devel/)的DateTool和NumberTool给模板使用。
FreeMarkerView和VelocityView的使用都有相应的ViewResolver支持,即FreeMarkerView- Resolver和VelocityViewResolver。不过,我们也可以在"面向多视图类型的ViewResolver"中使用它们。唯一需要注意的就是,使用这两种视图类型的时候,不要忘记通过FreeMarker- Configurer和VelocityConfigurer为它们提供渲染过程中使用的模板引擎支持。
3. 面向二进制文档格式的View实现
该类的View实现主要指Excel和PDF形式的文档视图,通过设定合适的contentType,并且本地有相应的应用程序的话,这些文档将可以在浏览器中直接打开,而不是下载保存。
对于Excel形式的视图,Spring MVC提供了如下两个抽象类的视图实现。
AbstractExcelView。使用Apache POI(http:// poi.apache.org/)来构建Excel文档的View实现类,支持读入Excel模板文件,子类需要实现buildExcelDocument(..)模板方法,以给出具体的模型数据到模板文件的合并逻辑。
AbstractJExcelView。该抽象类使用JExcel API(http:// www.andykhan.com/jexcelapi/)作为视图的渲染API,同样支持现有Excel模板文件的读入,具体子类也需要通过实现buildExcel- Document(..)模板方法,来实现具体的模型数据到Excel模板文件的合并过程。
两种面向Excel的View实现类都支持按照Locale读入不同的Excel模板文件,读入顺序类似于:
(1) fileLocation_zh_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实现允许我们输出JasperReport生成的相应格式的报表文件,包括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 ModelAndView(...);
mav.addObject("format","pdf");
...
return mav;
那么,JasperReportsMultiFormatView最终将通过JasperReportsPdfView输出PDF格式的报表文档。关于format的值与具体View实现类之间的关系,如表24-2所示。
表24-2 format的值与具体View实现类之间的关系
format值 |
具体View类型 |
csv |
JasperReportsCsvView |
html |
JasperReportsHtmlView |
JasperReportsPdfView |
|
xls |
JasperReportsXlsView |
24.5.3 自定义View实现
《Spring揭秘》第24章近距离接触Spring MVC主要角色,HandlerMapping、Controller、ModelAndView、ViewResolver和View可以算是Spring MVC框架中的"五虎将",它们共同组成了Spring MVC框架的强大躯干。本章对它们进行了详细的介绍,希望你完成本章内容之后,对它们已经了然于心了。本节为大家介绍自定义View实现。
AD:
24.5.3 自定义View实现
虽然Spring MVC框架已经提供了足够多的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去获取文件,直接继承Abstract- UrlBasedView或许更合适一些。当然,这完全视具体情况而定。代码清单24-33是我们完成的自定义View实现类StaticPdfView的代码演示。
代码清单24-33 用于静态PDF文件输出的自定义View实现类代码示例
1. public class StaticPdfView extends
2.
3. public
4. {
5. "application/pdf");
6. }
7.
8. @Override
9. protected void
10. throws
11. response.setContentType(getContentType());
12. new
13. OutputStream out = response.getOutputStream();
14. IOUtils.copy(ins, out);
15. out.flush();
16. IOUtils.closeQuietly(ins);
17. IOUtils.closeQuietly(out);
18. }
19. }
十分简单,不是吗?只是根据url信息读取文件,然后通过response输出到客户端即可。而View实现类里面所要做的不就是这些吗?根据具体场景构建视图内容,然后通过HttpServlet- Response的Writer或者OutputStream,将构建后的视图内容输出到客户端。就这点儿工作!
因为通常的ViewResolver实现都继承了AbstractViewResolver的默认开启缓存功能,所以,通过ViewResolver来查找并使用我们的StaticPdfView并非合适的方式。另外,当前场景中,StaticPdfView所需要的url信息是从数据库获取的,显然无法通过相应ViewResolver进行定义(当然,不排除其他场景下结合ViewResolver使用StaticPdfView的情形)。鉴于以上两点,在相应的Controller内部直接构造StaticPdfView的实例并返回,或许是比较合适的做法。也就是说,在Controller中是直接返回View实例还是返回逻辑视图名,需要根据情况权衡。在大多数情况下,推荐使用返回逻辑视图名的做法,但不排除直接返回View实例的情况。StaticPdfUrlViewController提供了一段代码示例,演示了StaticPdfView的使用,如代码清单24-34所示。
代码清单24-34 Controller内StaticPdfView的使用代码示例
1. public class StaticPdfUrlViewController extends
2.
3. @Override
4. protected
5. throws
6. new
7. View view = constructView(request);
8. mav.setView(view);
9. // 如果必要,添加其他模型数据
10. return
11. }
12.
13. private
14. String pdfFilePath = getPdfFileLocation(request);
15. new
16. view.setUrl(pdfFilePath);
17. return
18. }
19.
20. protected
21. // 根据请求参数从数据库中取得相应的url信息
22. String url = ...;
23.
24. return
25. }
26. }
方法getPdfFileLocation(..)将抽取Web请求参数,然后根据这些参数查询数据库,并返回对应的PDF文件路径。具体实现可能随前后台之间的参数约定而有少许差异。
注意 通常,自定义View实现类需要结合相应的ViewResolver才能使用,直接在Controller中实例化View并非大部分情况下的做法。对某类View来说,完全可以为其单独声明一个ViewResolver,指定合适的优先级别(通过order属性)。即使现用的ViewResolver无法满足需要,为某类View实现类提供自定义的ViewResolver实现类也并非难事。各位不妨考虑下,类似StaticPdfView这样的View实现,除了在Controller中直接实例化,是否可以为其提供一个自定义的ViewResolver呢?
现在你是否准备自定义自己的View实现类了呢?
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论