JSF 2简介,第1部分:流线化Web应用程序开发

日期: 2009-06-17 作者:David Geary 来源:TechTarget中国 英文

  随着2.0版本的发布,Java™Server Faces(JSF)现在可以轻松地实现健壮的、Ajax风格的Web应用程序。本文是共三部分的系列文章的开篇,JSF 2.0专家组成员David Geary将展示如何利用JSF 2中的新特性。在这期文章中,您将了解到如何使用JSF 2流线化开发,您将使用注释和约定代替XML配置,简化导航,并轻松访问资源。并且您将看到如何在您的JSF应用程序中使用Groovy。

  有关Web应用程序框架的最佳发源地,人们一直争论不休:是象牙塔(由理论家空想而来)还是现实世界。在后一种情况下,框架的诞生经受了实际需求的严酷考验。凭直觉判断,经受了实际需求的考验要胜过理论家的空想,并且我认为这种直觉完全经得起更进一步的检验。

  JSF 1就是在象牙塔中开发的,因此,它的出现并没有引起太大的轰动。但是,JSF做对了一件事情 — 它使市场上出现了大量来自实际开发的创新。早些时候,Facelets的初次登场成为了JavaServer Pages(JSP)的强有力候补。然后出现了Rich Faces,一个出色的JSF Ajax库;接着是ICEFaces,将Ajax和JSF联合起来的新颖方法;还有Seam、Spring Faces、Woodstock组件、JSF Templating等等。所有这些开源JSF项目都是由开发人员根据自己需要的功能构建的。

  JSF 2.0专家组实际上对来自这些开源项目的最佳特性进行了标准化。尽管JSF 2规范确实是由一些理论家编写的,但它也受到了来自实际开发的创新的驱动。回想起来,专家组的工作其实非常轻松,因为我们正站在巨人的肩膀上,比如Gavin King (Seam)、Alexandr Smirnov (Rich Faces)、Ted Goddard (ICEFaces)和Ken Paulson (JSF Templating)。实际上,所有这些巨人都是JSF 2专家组的成员。因此,JSF 2在许多方面都结合了象牙塔和真实世界的长处。并且它展示了这一点。JSF 2是对JSF 1的重大改进。

  本文是共三部分的系列文章的开篇,主要有两个目标:展示激动人心的JSF 2新特性,展示如何最佳地利用这些特性,这样您就能够利用JSF 2提供的功能。通过演示JSF 2的应用并伴随一些最佳使用技巧,我将阐述前面两个问题。下面是本文将要介绍的技巧:

  ·技巧1:去除XML配置
  ·技巧2:简化导航
  ·技巧3:使用Groovy
  ·技巧4:利用资源处理程序

  但是,我将首先介绍贯穿整个系列文章的示例应用程序。本文的应用程序源代码可以下载获得。

  基于强制映射的Web服务mashup示例

  图1展示了一个JSF mashup— 我将它称为places应用程序 — 它使用Yahoo! Web服务将地址转换为地图,并显示缩放级别和天气预报:

              

  图1. 从Yahoo! Web Services中查看地图和天气信息
 
  要创建一个地点,需要填写地址表单,激活Go按钮,然后应用程序将把地址发送给两个Web服务:Yahoo! Maps和Yahoo! Weather。

  Map服务在Yahoo!服务器上返回指向地址映射的11个地图URL,使用不同的缩放级别。Weather服务返回一些预先组装的HTML。图像URL和HTML内容都轻松地显示在一个JSF视图中,这要分别感谢<h:graphicImage>和<h:outputText>。

  places应用程序使您能够输入任意数量的地址。您甚至可以多次使用同一个地址 ,如图2所示,它实际上演示了缩放级别:

        

  图2. 缩放级别
 
  应用程序的关键点

  places应用程序有4个托管bean(managed bean),如表1所示:

      

  表1. places应用程序中的托管bean

  应用程序在会话范围内存储了一组Place,如图1所示,并在请求范围内维护了一个Place。应用程序还分别使用应用程序范围内的mapService和weatherService托管beans为Yahoo!的map和weather Web服务提供了简单的API。

  创建地点非常简单。清单1显示了图1中的视图所含的地址表单的代码:

  清单1. 地址表单
    
<h:form>
  <h:panelGrid columns=”2″>
    #{msgs.streetAddress} <h:inputText value=”#{place.streetAddress}” size=”15″/>
    #{msgs.city}          <h:inputText value=”#{place.city}”          size=”10″/>
    #{msgs.state}         <h:inputText value=”#{place.state}”         size=”2″/>
    #{msgs.zip}           <h:inputText value=”#{place.zip}”           size=”5″/>

    <h:commandButton value=”#{msgs.goButtonText}”
        style=”font-family:Palatino;font-style:italic”
        action=”#{place.fetch}”/>

  </h:panelGrid>
</h:form>
 
  当用户激活Go按钮并提交表单后,JSF将调用按钮的操作方法:place.fetch()。该方法将信息从Web服务发送到Place.addPlace(),后者创建一个新的Place实例,使用传递给方法的数据初始化实例,并将其存储在请求范围内。

  清单2展示了Place.fetch():

  清单2. Place.fetch()方法
    
public class Place {
  …
  private String[] mapUrls
  private String weather
  …
  public String fetch() {
    FacesContext fc = FacesContext.getCurrentInstance()
    ELResolver elResolver = fc.getApplication().getELResolver()

    // Get maps

    MapService ms = elResolver.getValue(
      fc.getELContext(), null, “mapService”)

    mapUrls = ms.getMap(streetAddress, city, state)

    // Get weather

    WeatherService ws = elResolver.getValue(
      fc.getELContext(), null, “weatherService”)

    weather = ws.getWeatherForZip(zip, true)

    // Get places

    Places places = elResolver.getValue(
      fc.getELContext(), null, “places”)

    // Add new place to places

    places.addPlace(streetAddress, city, state, mapUrls, weather)

    return null
  }
}
 
  Place.fetch()使用JSF的变量分解器(resolver)查找mapService和weatherService托管bean,并且使用这些托管bean从Yahoo! Web服务获得地图和天气信息。随后fetch()调用places.addPlace(),后者使用地图和天气信息以及地址,在请求范围内创建一个新的Place。

  注意fetch()返回null。由于fetch()是一个与按钮有关的操作方法,null返回值使得JSF重新加载同一个视图,其中显示用户会话中的所有位置,如清单3所示:

  清单3. 在视图中显示位置
    
<ui:composition
   
   
    >

  <h:form>
    <!– Iterate over the list of places –>
    <ui:repeat value=”#{places.placesList}” var=”place”>

      <div class=”placeHeading”>
        <h:panelGrid columns=”1″>

          <!– Address at the top –>
          <h:panelGroup>
            <div style=”padding-left: 5px;”>
              <i><h:outputText value=”#{place.streetAddress}”/></i>,
              <h:outputText value=”#{place.city}”/>
              <h:outputText value=”#{place.state}”/>
              <hr/>
            </div>
          </h:panelGroup>

          <!– zoom level prompt and drop down –>
          <h:panelGrid columns=”2″>
            <!– prompt –>
            <div style=”padding-right: 10px;margin-bottom: 10px;font-size:14px”>
              #{msgs.zoomPrompt}
            </div>

            <!– dropdown –>
            <h:selectOneMenu onchange=”submit()”
                 value=”#{place.zoomIndex}”
                 valueChangeListener=”#{place.zoomChanged}”
                 style=”font-size:13px;font-family:Palatino”>

              <f:selectItems value=”#{places.zoomLevelItems}”/>

            </h:selectOneMenu>
          </h:panelGrid>

          <!– The map –>
          <h:graphicImage url=”#{place.mapUrl}” style=”border: thin solid gray”/>

        </h:panelGrid>

        <!– The weather –>
        <div class=”placeMap”>
          <div style=”margin-top: 10px;width:250px;”>
            <h:outputText style=”font-size: 12px;”
              value=”#{place.weather}”
              escape=”false”/>
          </div>
        </div>
      </div>

    </ui:repeat>
  </h:form>

</ui:composition>
 
  清单3中的代码使用Facelets <ui:repeat>标记迭代用户会话中存储的位置列表。对于每个位置,输出应当如图3所示:

                        

  图3. 视图中显示的位置
 
  修改缩放级别

  zoom菜单(参见图3和清单3)有一个onchange=”submit()”属性,因此当用户选择某个缩放级别时,JavaScript submit()函数提交菜单的环绕(surrounding)表单。提交表单后,JSF调用菜单的相关值修改侦听器 — Place.zoomChanged()方法,如清单4所示:

  清单4. Place.zoomChanged()
    
public void zoomChanged(ValueChangeEvent e) {
  String value = e.getComponent().getValue()
  zoomIndex = (new Integer(value)).intValue()
}
 
  Place.zoomChanged()在一个名为zoomIndex的Place类的成员变量中存储缩放级别。由于导航不会受到与服务器通信的影响,JSF将重新加载页面,并且地图使用新的缩放级别进行更新,如下所示:<h:graphicImage url=”#{place.mapUrl}…”/>。当绘制地图时,JSF调用Place.getMapUrl(),它返回当前缩放级别下的地图URL,如清单5所示:

  清单5. Place.getMapUrl()
    
public String getMapUrl() {
  return mapUrls == null ? “” : mapUrls[zoomIndex]
}
 
  使用少量Facelets

  如果曾经使用过JSF 1,那么很可能会注意到本文的JSF 2代码中存在一些细微的差别。首先,我使用了JSF 2的新的显示技术 — Facelets — 而不是JSP。您将从本系列后续文章中看到,Facelets提供了许多强大的特性来帮助您实现健壮、灵活和可扩展的用户界面。但是在前面的代码清单中,我并没有过多利用这种功能。然而,Facelets为JSF带来的众多微小改进之一便是能够将JSF值表达式直接放入到XHTML页面;例如,在清单1中,我将#{msgs.city}等表达式直接放入页面中。如果使用JSF 1,则必须将表达式封装到<h:outputText>中,例如<h:outputText value=”#{msgs.city}”/>。但要注意,出于安全考虑,必须始终将来自用户输入的文本进行转义,例如,在清单3中我使用了<h:outputText>,它在默认情况下转义其文本来显示位置信息。

  从Facelets角度来讲,还需要注意清单3中的<ui:composition>标记。该标记指定清单3中的XHTML页面将被包含到其他XHTML页面中。Facelets composition是Facelets templating的中心组件,类似于流行的Apache Tiles框架。在本文的后续文章中,我将讨论Facelets模板并展示如何根据Composed Method Smalltalk模式构建您的视图。

  目前为止,前面的代码并没有使用Facelets,与JSF 1相比没有出现显著的变化。现在,我将展示更加大的差异。第一个比较大的差异体现在将要为JSF 2应用程序编写的XML配置的数量方面。

  技巧1:去掉XM 配置

  Web应用程序的XML配置始终是个麻烦问题 — 它非常冗长并且容易出现错误,因此最好将XML配置委托给一个框架,比如通过注释、约定或特定于领域的语言。作为开发人员,我们应该能够集中精力实现一些操作,而不是将浪费时间在冗长的XML方面。

  作为一个典型的例子,清单6展示了在使用JSF 1的情况下,在places应用程序中声明托管bean所需的20行XML代码:

  清单6. JSF 1的托管bean声明
    
<managed-bean>
  <managed-bean-class>com.clarity.MapService</managed-bean-class>
  <managed-bean-name>mapService</managed-bean-name>
  <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

<managed-bean>
  <managed-bean-class>com.clarity.WeatherService</managed-bean-class>
  <managed-bean-name>weatherService</managed-bean-name>
  <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

<managed-bean>
  <managed-bean-class>com.clarity.Places</managed-bean-class>
  <managed-bean-name>places</managed-bean-name>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<managed-bean>
  <managed-bean-class>com.clarity.Place</managed-bean-class>
  <managed-bean-name>place</managed-bean-name>
  <managed-bean-scope>request</managed-bean-scope>
</managed-bean>
 
  对于JSF 2,XML消失了,您将对类使用注释,如清单7所示:

  清单7. JSF 2的托管bean注释
    
@ManagedBean(eager=true)
public class MapService {
  …
}

@ManagedBean(eager=true)
public class WeatherService {
  …
}

@ManagedBean()
@SessionScoped
public class Places {
  …
}

@ManagedBean()
@RequestScoped
public class Place {
  …
}
 
  按照约定,托管bean的名称与类名相同,类名的第一个字母被转换为小写。例如,清单7中创建的托管,从上到小依次为:mapService、weatherService、places和place。也可以使用ManagedBean注释的name属性显式地指定一个托管bean,比如:@ManagedBean(name = “place”)。

  在清单7中,我对mapService和webService托管bean使用eager属性。当eager属性为true时,JSF将在启动时创建托管bean并将其放入应用程序范围。

  也可以使用@ManagedProperty注释设置托管bean属性。表2展示了JSF 2托管bean注释的完整列表:

       

  表2. JSF 2托管bean注释(@…Scoped注释只对@ManagedBean有效)

  技巧2:简化导航

  在JSF 1中,导航使用XML指定。比如,要从login.xhtml转到places.xhtml,可能使用清单8所示的导航规则:

  清单8. JSF 1中的导航配置规则和用例
    
<navigation-rule>
  <navigation-case>
    <from-view-id>/pages/login.xhtml</from-view-id>
    <outcome>places</outcome>
    <to-view-id>/pages/places.xhtml</to-view-id>
  </navigation-case>
</navigation-rule>
 
  要去除清单8中的XML,可以利用JSF 2的导航约定:JSF将.xhtml添加到按钮操作的末尾并加载该文件。这意味着不需要使用注释或其他内容,只需要使用约定就可以完整地避免编写导航规则的需求。在清单9在,按钮的操作是places,因此JSF加载places.xhtml:

  清单9. 通过约定进行导航
    
<h:commandButton id=”loginButton”
  value=”#{msgs.loginButtonText}”
  action=”places”/>
 
  对于清单9来说,不需要任何导航XML。清单9中的按钮加载places.xhtml,但是前提是该文件和按钮所在的文件处于同一个目录中。如果操作并没有以斜杠(/)开头,那么JSF认为这是一个相对路径。如果需要更加明确一点,可以指定一个绝对路径,如清单10所示:

  清单10. 使用绝对路径的导航
    
<h:commandButton id=”loginButton”
  value=”#{msgs.loginButtonText}”
  action=”/pages/places”/>
 
  当用户激活清单10中的按钮时,JSF将加载/pages/places.xhtml文件。

  默认情况下,JSF将从一个XHTML页面转至另一个XHTML页面,但是通过指定faces-redirect参数可以重定向,如清单11所示:

  清单11. 通过重定向进行导航
    
<h:commandButton id=”loginButton”
  value=”#{msgs.loginButtonText}”
  action=”places?faces-redirect=true”/>
 
  技巧3:使用Groovy

  Java技术的最大优势并不是Java语言,而是Java虚拟机(JVM)。在JVM上运行着强大、新颖和创新的语言,比如Scala、JRuby和Groovy,这使您在编写代码时拥有了更多选择。Groovy这个名字有些奇怪,但是功能非常强大,融合了Ruby、Smalltalk和Java语言,它是这些语言中最为流行的一种语言。

  使用Groovy的理由有很多。首先,它要比Java语言更加简洁、功能更加强大。还有两个原因:不使用分号,不需要强制转换(casting)。

  您可能还没有注意到,在清单2中,Place类是使用Groovy编写的。这一点可以通过代码中没有使用分号看出来,但是注意下面这行代码:MapService ms = elResolver.getValue(…)。对于Java代码,我必须强制转换ElResolver.getValue()的结果,因为该方法返回类型Object。Groovy可以为我自动完成转换。

  可以将Groovy用于任何使用Java代码编写的JSF工件 — 例如,组件、呈现器、验证器和转换器。事实上,这对于JSF 2来说并不新鲜 — 因为Groovy源文件编译为Java字节码,您只需使用Groovy生成的.class文件,就好象它们是由javac生成的一样。当然,Groovy生成的.class文件可以正常工作后,需要了解如何热部署Groovy源代码,并且对于Eclipse用户,答案非常简单:下载并安装Groovy Eclipse插件。Mojarra是Sun的JSF实现,从版本1.2_09之后提供了对Groovy的明确支持。

  技巧4:利用资源处理程序

  JSF 2提供了定义和访问资源的标准机制。您将自己的资源放到名为resources的顶级目录下,并使用一些JSF 2标记来在视图中访问这些资源。例如,图4展示了places应用程序的资源:

                             

  图4. places应用程序的资源
 
  对资源的惟一需求是它必须位于resources目录或resources目录的子目录中。可以随意命名resources目录的子目录。

  在您的视图代码中,可以使用两个JSF 2标记访问资源:<h:outputScript>和<h:outputStylesheet>。这些标记可以结合用于JSF 2的<h:head>和<h:body>标记,如清单12所示:

  清单12. 在XHTML中访问资源
    
<html
   
    >

  <h:head>
    …
  </h:head>

  <h:body>
    <h:outputStylesheet library=”css” name=”styles.css” target=”body”/>
    <h:outputScript library=”javascript” name=”util.js” target=”head”/>
    …
  </h:body>
</html>
 
  <h:outputScript>和<h:outputStylesheet>标记有两个属性,分别指定了脚本或样式表:library和name。library名称对应于resources目录下的子目录,这是保存资源的位置。例如,如果在resources/css/en目录中有一个样式表,那么library将为css/en。name属性是资源本身的名称。

  可重新定位的资源

  开发人员需要能够在页面中指定想要呈现他们的资源的位置。例如,如果将JavaScript放在页面体中,浏览器将在加载页面时执行JavaScript。另一方面,如果将JavaScript放到页面的头部,那么JavaScript只有在得到调用时才会被执行。由于资源的放置位置会影响它的使用方式,因此需要能够指定希望在哪些位置显示资源。

  JSF 2资源是可重新定位的,这意味着您可以在页面中指定希望放置资源的位置。您将使用target属性指定位置;比如,在清单12中,我将CSS放到页面体中,而将JavaScript放到页面头部中。

  有些情况下,需要使用JSF表达式语言(EL)访问资源。比如,清单13展示了如何使用<h:graphicImage>访问一个图像:

  清单13. 使用JSF表达式语言访问资源
    
  <h:graphicImage value=”#{resource[‘images:cloudy.gif’]}”/>
 
  在EL表达式内访问资源的语法是resource[‘LIBRARY:NAME’],其中LIBRARY和NAME对应于<h:outputScript>和<h:outputStylesheet>标记的library和name属性。

  结束语

  到目前为止,我仅仅触及了JSF 2特性中最浅显的内容,包括托管bean、注释、简化导航和资源支持。在本系列随后的两篇文章中,我将探讨Facelets、JSF 2的复合组件以及对Ajax的内置支持。

  关于作者

  David Geary是一名作家、演讲家和顾问,也是Clarity Training, Inc.的总裁,他指导开发人员使用JSF和Google Web Toolkit(GWT)实现Web应用程序。他是JSTL 1.0和JSF 1.0/2.0专家组的成员,与人合作编写了Sun的Web Developer认证考试的内容,并且为多个开源项目作出贡献,包括Apache Struts和Apache Shale。David的Graphic Java Swing一直是有关Java的畅销书籍,而Core JSF(与Cay Horstman合著)是有关JSF的畅销书。David经常在各大会议和用户组发表演讲。他从2003年开始就一直是NFJS tour的固定演讲人,并且在Java University教授课程,两次当选为JavaOne之星。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐