使用其他视图技术

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

7.5  使用其他视图技术

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的声明删除。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <servlet>

        <servlet-name>dispatcher</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</ servlet-class>

        <load-on-startup>0</load-on-startup>

    </servlet>

    <servlet-mapping>

        <servlet-name>dispatcher</servlet-name>

        <url-pattern>*.html</url-pattern>

    </servlet-mapping>

</web-app>

然后修改dispatcher-servlet.xml,添加velocityConfigurer配置Velocity引擎,并设置viewResolver为VelocityViewResolver。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="/test.html" class="example.chapter7.TestController" />

    <!-- 使用Velocity视图解析器 -->

    <bean id="viewResolver" class="org.springframework.web.servlet.view. velocity.VelocityViewResolver">

        <property name="contentType" value="text/html;charset=UTF-8" />

        <property name="prefix" value="/" />

        <property name="suffix" value=".html" />

    </bean>

    <!-- 配置Velocity -->

    <bean id="velocityConfig" class="org.springframework.web.servlet.view. velocity.VelocityConfigurer">

        <!-- 配置文件位置 -->

        <property name="configLocation" value="/WEB-INF/velocity.properties" />

        <!-- 视图资源位置 -->

        <property name="resourceLoaderPath" value="/" />

    </bean>

</beans>

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 = <!-- include error :

directive.include.output.errormsg.end   =  see error log -->

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

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <title>Spring_Velocity</title>

</head>

<body>

    <h3>Hello, ${name}, it is ${time}</h3>

</body>

</html>

虽然 Velocity一般使用.vm作为页面的扩展名,不过,Velocity视图是纯HTML页面,因此我们使用.html作为扩展名,这样在可视化 HTML编辑器中编辑时非常直观,并且可以在不启动服务器的情况下就能预览页面效果,大大简化了页面的设计和实现,如图7-42所示。

启动Resin服务器,输入http://localhost/test.html,可以看到由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。

<!-- 使用Freemarker视图解析器 -->

<bean id="viewResolver" class="org.springframework.web.servlet.view. freemarker.FreeMarkerViewResolver">

    <property name="contentType" value="text/html;charset=UTF-8" />

    <property name="prefix" value="/" />

    <property name="suffix" value=".html" />

</bean>

<!-- 配置Freemarker -->

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view. freemarker.FreeMarkerConfigurer">

    <!-- 视图资源位置 -->

    <property name="templateLoaderPath" value="/" />

    <property name="defaultEncoding" value="UTF-8" />

</bean>

模板test.html可以稍做修改,加入Freemarker内置的格式化功能来定制Date类型的输出格式。

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <title>Spring_Freemarker</title>

</head>

<body>

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

</body>

</html>

添加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);

        // 添加<name>:

        String name = (String)model.get("name");

        Element nameNode = document.createElement("name");

        nameNode.appendChild(document.createTextNode(name));

        root.appendChild(nameNode);

        // 添加<time>:

        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。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="/test.html" class="example.chapter7.TestController" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view. ResourceBundleViewResolver">

        <property name="basename" value="views" />

    </bean>

</beans>

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

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" />

    <xsl:template match="/">

        <html><head>

            <title>Spring_Xslt</title>

        </head>

        <body>

            <h3>Hello,

                <xsl:value-of select="DocRoot/name" />, it is

                <xsl:value-of select="DocRoot/time" /></h3>

        </body></html>

    </xsl:template>

</xsl:stylesheet>

将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<String, ViewResolver> resolvers;

    public void setResolvers(Map<String, ViewResolver> 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如下。

<bean id="viewResolver" class="example.chapter7.MixedViewResolver">

    <property name="resolvers">

        <map>

            <entry key="jsp">

                <bean class="org.springframework.web.servlet.view.Internal ResourceViewResolver">

                    <property name="viewClass" value="org.springframework. web.servlet.view.JstlView" />

                </bean>

            </entry>

            <entry key="vm">

                <bean class="org.springframework.web.servlet.view.velocity. VelocityViewResolver">

                    <property name="contentType" value="text/html;charset=UTF-8" />

                </bean>

            </entry>

            <entry key="ftl">

                <bean class="org.springframework.web.servlet.view.freemarker. FreeMarkerViewResolver">

                    <property name="contentType" value="text/html;charset=UTF-8" />

                </bean>

            </entry>

        </map>

    </property>

</bean>

扩展名“.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服务器,分别输入“http://localhost: 8080/test.html?view=jsp”、“http://localhost:8080/test. html?view=vm”和“http://localhost: 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}获得属性值,而非复杂的<c:out value="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功能,还能极大地简化分页等功能的实现。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值