分类:
2008-10-15 11:36:42
Spring的MVC框架被设计为控制器和具体视图技术的完全解耦。控制器只需要返回视图的逻辑名称,至于如何解析这个名称,由ViewResolver负责,因此,从一种视图技术(例如,JSP)转到另一种视图技术时,控制器无需更改,只要设定合适的ViewResolver即可。
下面要介绍的是除了JSP之外的其他视图技术。Spring提供了对Velocity、Freemaker和XSLT的完善支持。
Velocity是一种模板技术,能够通过模板生成任何文本内容,因此也非常适合作为视图。Velocity提供了一种非常简单的模板语言VTL,很容易同时被Java开发人员和网页设计人员轻松理解,和JSP相比,Velocity在以下几点更具优势。
(1)Velocity不提供Java代码支持,这意味着它只能作为视图,无法嵌入任何逻辑代码。Velocity的这种设计就是为了强制分离Java逻辑和Web页面,使Web应用程序更容易维护,而JSP允许嵌入任意的Java代码,很容易造成Java代码的滥用。
(2)Velocity不需要特定的标签,它使用简单的${var_name}来标记变量,这使得页面设计更加容易,因为JSP的标签代码通常在可视化的HTML编辑器中无法正常显示。Velocity页面还能使用任意扩展名,包括.html,因此,可以直接在浏览器中预览页面的效果。
(3)通常,Velocity还提供了比JSP更快的渲染速度。
要在Spring MVC框架中应用Velocity,首先需要配置Velocity引擎。在Spring中使用Velocity比单独使用Velocity更加容易,也更加方便,我们甚至根本不需要与Velocity的API打交道,所有Velocity相关组件均是在XML配置文件中定义的。我们将Spring MVC项目复制一份,命名为Spring_Velocity,结构如图7-41所示。
Velocity需要velocity.jar和commons-collections.jar这两个jar包,将其与Spring的相关jar包一同放入/web/WEB-INF/lib目录下。由于Velocity不需要taglib,修改web.xml,将taglib的声明删除。
"">
然后修改dispatcher-servlet.xml,添加velocityConfigurer配置Velocity引擎,并设置viewResolver为VelocityViewResolver。
xmlns:xsi="" xsi:schemaLocation=" /spring-beans.xsd">
Velocity还需要一个配置文件,用于设置Velocity引擎。配置文件的每个选项都可以注入到Spring的XML配置文件中,但是,由于Velocity的配置选项较多,放在单独的velocity.properties中比较合适。典型的配置如下,读者只需要注意几个重要的配置选项。
runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log = example.chapter7
runtime.log.error.stacktrace = true
runtime.log.warn.stacktrace = true
runtime.log.info.stacktrace = false
runtime.log.invalid.reference = true
# 设置输入/输出的编码:
input.encoding = UTF-8
output.encoding = UTF-8
directive.foreach.counter.name = velocityCount
# 设置foreach循环的index初始值:
directive.foreach.counter.initial.value = 1
directive.include.output.errormsg.start =
directive.parse.max.depth = 3
# 设置输入模板来自文件:
resource.loader = file
file.resource.loader.description = Velocity-File-Resource-Loader
# 设置模板的加载类:
file.resource.loader.class = org.apache.velocity.runtime.resource.loader. FileResourceLoader
# 设置是否使用cache,在开发期可禁用cache以便随时编辑页面:
file.resource.loader.cache = false
# 设置检测文件改动的时间间隔,在运行期可设置较大的数,如3600(1小时):
file.resource.loader.modificationCheckInterval = 1
# 设置macro文件位置:
velocimacro.library = /macro.txt
velocimacro.library.autoreload = true
Velocity使用一种VTL语言来渲染视图,VTL有非常简单的语法,它通过${var_name}来输出变量,支持循环,条件判断和赋值。将test.jsp重命名为test.html,修改内容如下。
虽然Velocity一般使用.vm作为页面的扩展名,不过,Velocity视图是纯HTML页面,因此我们使用.html作为扩展名,这样在可视化HTML编辑器中编辑时非常直观,并且可以在不启动服务器的情况下就能预览页面效果,大大简化了页面的设计和实现,如图7-42所示。
启动Resin服务器,输入,可以看到由Velocity渲染的页面,如图7-43所示。
图7-42 图7-43
Velocity还提供了Macro(宏)的功能,能将反复引用的一组复杂的HTML/Velocity代码通过一个Macro定义并引用,极大地简化页面的设计。Macro相当于JSP的自定义标签,但JSP的自定义标签编写极其复杂,而用户编写的Macro仍是HTML/Velocity代码,因此相对容易得多。
Freemaker是取代JSP的又一种视图技术,和Velocity非常类似,但是它比Velocity多了一个格式化的功能,因此使用上较Velocity方便一点,但语法也稍微复杂一些。
将Velocity替换为Freemarker只需要改动一些配置文件,同样,在Spring中使用Freemarker也非常方便,根本无须与Freemarker的API打交道。我们将Spring_Velocity工程复制一份,命名为Spring_Freemarker,结构如图7-44所示。
图7-44
修改dispatcher-servlet.xml,将velocityConfig删除,修改viewResolver为FreeMarker ViewResolver,并添加一个freemarkerConfig。
模板test.html可以稍做修改,加入Freemarker内置的格式化功能来定制Date类型的输出格式。
添加freemarker.jar到web/WEB-INF/lib目录后,启动Resin,可以看到由Freemarker渲染的页面,如图7-45所示。
图7-45
XSLT(XSL是eXtensible Stylesheet Language的缩写,而XSLT代表XSL Transformations)技术是为了将XML格式的文档转化为任意格式的文本文档,当然,主要用途之一就是将XML转化为HTML。使用XML+XSLT能实现最严格的数据和显示分离:XML仅包含数据,而最终的渲染页面则交给XSLT来完成,如图7-46所示。
图7-46
XSLT可以实现XML到HTML的转化,转化过程既可以在服务器端完成,也可以在浏览器端完成。如果在服务器端转化,则客户端根本不知道服务器端使用的具体技术,不过,由于XSLT的转化非常耗费CPU资源,因此,在访问量大的情况下,要考虑服务器是否能够负载,相比之下,在浏览器端转化就不需要耗费太多的服务器资源,服务器只需要向浏览器传送XML和XSLT文件,由浏览器自己完成XML到HTML的转化,这种方式减轻了服务器的负担,不过,客户端就得知了服务器端采用的技术,并且早期的浏览器还不支持XML的转化,但是现在绝大多数浏览器都没有问题。在浏览器端转化的另一个问题是搜索引擎无法正确地抓取页面,因为搜索引擎一般只抓取HTML格式的页面,对于仅包含纯数据的XML,搜索引擎一般无法分析其内容。
在Spring中使用XSLT和其他视图技术类似,我们在Eclipse中新建一个Spring_Xslt工程,结构如图7-47所示。
图7-47
其中,TestController不变,test.html更改为test.xslt,负责将XML转化为HTML。这里的一个问题是,Spring的Controller返回的Model是Map类型,必须将其首先转化为XML才能用XSLT完成到HTML的转化。遗憾的是,从Map类型到XML没有直接的转化过程,由于Map中包含的数据类型也各不相同,因此,还必须手动编写TestXsltView类,完成Map到XML的转化,该类必须从AbstractXsltView派生,并且复写createXsltSource()方法。
public class TestXsltView extends AbstractXsltView {
protected Source createXsltSource(Map model, String rootName, HttpServletRequest request, HttpServletResponse response) throws Exception {
Document document = DocumentBuilderFactory.newInstance().newDocument Builder().newDocument();
Element root = document.createElement(rootName);
// 添加
String name = (String)model.get("name");
Element nameNode = document.createElement("name");
nameNode.appendChild(document.createTextNode(name));
root.appendChild(nameNode);
// 添加
Date time = (Date)model.get("time");
Element timeNode = document.createElement("time");
timeNode.appendChild(document.createTextNode(time.toString()));
root.appendChild(timeNode);
return new DOMSource(root);
}
}
为了使用XSLT,在dispatcher-servlet.xml中配置使用ResourceBundleViewResolver作为ViewResolver。
xmlns:xsi="" xsi:schemaLocation=" /spring-beans.xsd">
定义了Map到XML的转化后,还需要一个views.properties配置文件来告诉Spring该TestXsltView生成的XML应该用哪个XSLT来完成到HTML的转化,该文件必须放到src目录下。
test.class=example.chapter7.TestXsltView
#test.stylesheetLocation=/test.xslt
test.root=DocRoot
我们先把test.stylesheetLocation注释掉,这样Spring就可以直接将XML返回,我们首先检查生成的XML是否正确。启动Resin,输出如图7-48所示。
图7-48
现在,我们编写test.xslt来转化生成的XML为HTML,放到web目录之下。
将views.properties的注释去掉,然后重新启动Resin,可以看到由XSLT转化而成的HTML,如图7-49所示。
图7-49
一般来说,如果使用XSLT,由于视图部分总是要先设计好HTML页面,然后才能编写XSLT来转化XML到指定的HTML格式。如果HTML页面结构发生了较大的改动,则整个XSLT都必须全部重写,其维护成本是非常高的。目前,在还没有任何可视化编辑器辅助设计的情况下,不推荐使用XSLT。如果应用程序本身已经有了XML数据,则可以考虑使用XSLT来完成HTML转化。
如果需要混合使用多种视图技术,就需要设置合适的ViewResolver,并且让其有能力解析不同的视图名称。
以Web形式启动的Spring应用程序会自动查找所有具有ViewResolver接口的Bean,将它们作为一个视图解析链来使用。默认地,声明在前的ViewResolver具有优先解析视图的权力,也可以用order属性来定义ViewResolver的解析顺序。
Spring会按照ViewResolver的解析链来让每个ViewResolver试图解析并返回一个View,如果某个ViewResolver返回了View,则解析过程结束,然后调用View对象的render()方法开始真正的渲染任务。例如,InternalResourceViewResolver会返回JstlView视图,VelocityViewResolver会返回VelocityView,FreeMarkerViewResolver会返回FreeMarkerView,不同的View会有不同的渲染方式。
不过,某些情况下,一个ViewResolver可能无法根据视图的逻辑名检测到一个View是否存在。例如,InternalResourceViewResolver只能调用RequestDispatcher来检测JSP文件是否存在,不幸的是,该方法只能调用一次,这可能会使后续的ViewResolver的解析出现问题。
另一个不太令我们满意的原因是使用ViewResolver链效率较低,它是通过循环来实现的,可以在DispatcherServlet中看到源代码。
for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
ViewResolver viewResolver = (ViewResolver) it.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
我们最好不要让ViewResolver链来解析视图,因为这样每个ViewResolver只能依次“猜测”View是否存在,如果能根据视图的扩展名来决定由哪个ViewResolver来解析,则只需要一步就可完成视图的解析。
一个可行的方法是实现一个自定义的MixedViewResolver,并直接和Spring MVC框架关联,然后MixedViewResolver根据视图的扩展名来决定将解析委托给哪个具体的ViewResolver。例如,对于扩展名为.jsp的视图,就用InternalResourceViewResolver解析;对于扩展名为.vm的视图,就用VelocityViewResolver解析;对于扩展名为.ftl的视图,就用FreeMarkerViewResolver来解析。这样,就实现了混合使用多种视图技术。
MixedViewResolver仅实现ViewResolver接口,其实现的关键是将视图扩展名和具体的ViewResolver实例关联起来,从而实现根据视图扩展名来“转发”解析请求。
public class MixedViewResolver implements ViewResolver {
private Map
public void setResolvers(Map
this.resolvers = resolvers;
}
public View resolveViewName(String viewName, Locale locale) throws Exception {
int n = viewName.lastIndexOf('.');
if(n==(-1))
throw new NoSuchViewResolverException();
// 获得扩展名:
String suffix = viewName.substring(n+1);
// 取出对应的ViewResolver:
ViewResolver resolver = resolvers.get(suffix);
if(resolver!=null)
return resolver.resolveViewName(viewName, locale);
// 没有找到对应的ViewResolver就抛异常:
throw new NoSuchViewResolverException("No ViewResolver for " + suffix);
}
}
由于需要根据视图的扩展名来决定到底使用何种ViewResolver,所以就不能配置ViewResolver的suffix属性,可以配置一个prefix属性,但是本例中为了简化,始终让Controller返回绝对路径的视图。
在dispatcher-servlet.xml配置文件中,定义viewResolver如下。
扩展名“.jsp”、“.vm”和“.ftl”分别对应3种ViewResolver。当然,velocityConfig和freemarkerConfig的定义也必不可少。然后修改TestController,我们根据URL的参数来决定返回的视图名称。
public ModelAndView handleRequest(HttpServletRequest request, HttpServlet Response response) throws Exception {
String view = request.getParameter("view");
if(view==null)
view = "jsp"; // 默认使用jsp
String name = request.getParameter("name");
if(name==null)
name = "spring";
Map model = new HashMap();
model.put("name", name);
model.put("time", new Date());
// "/test." + view 就是视图的完整路径:
return new ModelAndView("/test." + view, model);
}
我们在web/目录下分别放置了test.jsp、test.vm和test.ftl这3个视图文件,整个Spring_Mixed工程的结构如图7-50所示。
图7-50
配置好web.xml(不要忘记了声明JSP的taglib)和velocity.properites,确保所有的jar包都在WEB-INF/lib目录下,编译工程,启动Resin服务器,分别输入“ 8080/test.html?view=jsp”、“8080/test. html?view=vm”和“ 8080/test.html? view=ftl”,观察浏览器输出,如图7-51~图7-53所示。
图7-51
图7-52 图7-53
可以看到,对于TestController返回的同一个Model,Spring MVC使用了3个不同的ViewResolver来解析并渲染视图。
从AbstractCachingViewResolver继承而来的ViewResolver还可以具有Cache的能力,将解析过的视图缓存起来,某些情况下能大大提高解析速度。但是,在MixedViewResolver中我们并不需要缓存功能,因为其包含的其他ViewResolver可能已经具有缓存的功能了。MixedViewResolver仅仅实现“转发”解析请求的功能,如果实际负责解析的ViewResolver具有缓存的功能,则缓存就会生效,因为相同的视图名称总是会转发给同一个ViewResolver。
我们已经成功地让多种视图技术共存于Spring MVC框架中,这得益于Spring MVC框架极为灵活的低耦合设计,并且基于接口的设计使我们能够非常方便地插入自己的实现,从而扩展Spring的功能。
JSP作为标准的JavaEE规范之一,已经获得了广泛的使用,然而,由于可以嵌入Java代码,如果不严格控制Controller和View的界限,很容易造成JSP页面混入逻辑代码,导致Web应用程序的维护成本大幅上升。此外,如果大量使用标签库,也容易造成页面布局混乱,使页面设计人员难以在可视化编辑器中设计页面。JSP 2.0标准已经开始支持类似Velocity的EL语法,即采用${var.name}获得属性值,而非复杂的
Velocity最大的优势是非常简单的页面,由于不可嵌入Java代码,因此强制实现了MVC架构。
Freemarker与Velocity非常类似,但多了一个格式化的功能,因此,使用上可能稍微方便一点。
XSLT是XML技术的一部分,从本质上来讲,只有XML+XSLT才能真正实现数据的内容和显示相分离,然而,就目前而言,支持XSLT的工具还非常少,编写XSLT极其困难,因此不推荐采用XSLT作为视图。
在实际项目中,采用何种视图要根据需求而定。考虑到设计页面的通常是设计人员而非开发人员,因此,页面设计要尽量做到纯HTML格式,能使用可视化HTML编辑器设计,而Velocity和Freemarker无疑在这方面更胜一筹。在本书的Live在线书店应用中,视图技术就采用了Velocity而非标准的JSP。读者可以看到,设计一个Velocity视图和设计一个HTML页面几乎没有什么区别,借助于Velocity的Macro功能,还能极大地简化分页等功能的实现。