Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1964582
  • 博文数量: 606
  • 博客积分: 9991
  • 博客等级: 中将
  • 技术积分: 5725
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-17 19:07
文章分类

全部博文(606)

文章存档

2011年(10)

2010年(67)

2009年(155)

2008年(386)

分类:

2008-10-15 11:36:42

Spring的MVC框架被设计为控制器和具体视图技术的完全解耦。控制器只需要返回视图的逻辑名称,至于如何解析这个名称,由ViewResolver负责,因此,从一种视图技术(例如,JSP)转到另一种视图技术时,控制器无需更改,只要设定合适的ViewResolver即可。

下面要介绍的是除了JSP之外的其他视图技术。Spring提供了对Velocity、Freemaker和XSLT的完善支持。

7.5.1  Velocity

Velocity是一种模板技术,能够通过模板生成任何文本内容,因此也非常适合作为视图。Velocity提供了一种非常简单的模板语言VTL,很容易同时被Java开发人员和网页设计人员轻松理解,和JSP相比,Velocity在以下几点更具优势。

(1)Velocity不提供Java代码支持,这意味着它只能作为视图,无法嵌入任何逻辑代码。Velocity的这种设计就是为了强制分离Java逻辑和Web页面,使Web应用程序更容易维护,而JSP允许嵌入任意的Java代码,很容易造成Java代码的滥用。

文本框:  
图7-41
(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

        org.springframework.web.servlet.DispatcherServlet

        0

   

   

        dispatcher

        *.html

   

然后修改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,修改内容如下。

   

    Spring_Velocity

   

Hello, ${name}, it is ${time}

虽然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代码,因此相对容易得多。

7.5.2  Freemarker

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类型的输出格式。

   

    Spring_Freemarker

   

Hello, ${name}, it is ${time?string("yyyy-MM-dd HH:mm:ss")}

添加freemarker.jar到web/WEB-INF/lib目录后,启动Resin,可以看到由Freemarker渲染的页面,如图7-45所示。

图7-45

7.5.3  XSLT

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目录之下。

   

   

       

            Spring_Xslt

       

       

           

Hello,

                , it is

               

       

   

将views.properties的注释去掉,然后重新启动Resin,可以看到由XSLT转化而成的HTML,如图7-49所示。

图7-49

一般来说,如果使用XSLT,由于视图部分总是要先设计好HTML页面,然后才能编写XSLT来转化XML到指定的HTML格式。如果HTML页面结构发生了较大的改动,则整个XSLT都必须全部重写,其维护成本是非常高的。目前,在还没有任何可视化编辑器辅助设计的情况下,不推荐使用XSLT。如果应用程序本身已经有了XML数据,则可以考虑使用XSLT来完成HTML转化。

7.5.4  混合使用多种视图技术

如果需要混合使用多种视图技术,就需要设置合适的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 resolvers;

    public void setResolvers(Map resolvers) {

        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的功能。

7.5.5  几种视图技术的比较

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功能,还能极大地简化分页等功能的实现。

阅读(812) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~