亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

JSF 2 簡介,第 1 部分: 流線化 Web 應用程序開

系統 1977 0

JSF 2 簡介,第 1 部分: 流線化 Web 應用程序開發

使用 JSF 2 簡化導航、免除 XML 配置并輕松訪問資源


級別: 中級

David Geary , 總裁, Clarity Training, Inc.

2009 年 6 月 15 日

隨著 2.0 版本的發布,Java?Server Faces (JSF) 現在可以輕松地實現健壯的、Ajax 風格的 Web 應用程序。本文是共三部分的系列文章的開篇,JSF 2.0 專家組成員 David Geary 將展示如何利用 JSF 2 中的新特性。在這期文章中,您將了解到如何使用 JSF 2 流線化開發,您將使用注釋和約定代替 XML 配置,簡化導航,并輕松訪問資源。并且您將看到如何在您的 JSF 應用程序中使用 Groovy。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

有關 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 中查看地圖和天氣信息
從 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
托管 bean 名稱 類 范圍
mapService com.clarity.MapService 應用程序
weatherService com.clarity.WeatherService 應用程序
places com.clarity.Places 會話
place com.clarity.Place 請求
運行 places 應用程序

要運行 places 應用程序,需要訪問 developer.yahoo.com/maps/ajax ,從 Yahoo! 獲得一個應用程序 ID,這樣才能使用 Yahoo! Web 服務。單擊 Yahoo! Maps Web Service 中的 Get an App ID 按鈕。得到 ID 后,在 MapService.java WeatherService.java 中用您的 ID 替換 YOUR_ID_HERE

應用程序在會話范圍內存儲了一組 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 xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">

<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:去掉 XML 配置

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 有效)
托管 bean 注釋 描述 屬性
@ManagedBean

以托管 bean 的形式注冊一個類實例,然后將其放入到使用其中一個 @...Scoped 注釋指定的范圍內。如果沒有指定任何范圍,JSF 將把此 bean 放入請求范圍,如果沒有指定任何名稱,JSF 將把類名的第一個字母轉換為小寫,形成一個托管 bean 名稱;例如,如果類名為 UserBean ,那么 JSF 將創建一個托管 bean,其名為 userBean 。 eager name 屬性都是可選的。

注釋必須結合使用一個實現零參數構造器的 Java 類。

eager , name
@ManagedProperty

為托管 bean 設置一個屬性。注釋必須放在類成員變量的聲明之前。 name 屬性指定特性的名稱,默認情況下為成員變量的名稱。 value 屬性是特性的值,可以是一個字符串,也可以是一個 JSF 表達式,比如 #{...} 。

value , name
@ApplicationScoped 在應用程序范圍內存儲托管 bean。
@SessionScoped 在會話范圍內存儲托管 bean。
@RequestScoped 在請求范圍內存儲托管 bean。
@ViewScoped 在視圖范圍內存儲托管 bean。
@NoneScoped 將托管 bean 指定為沒有范圍。無范圍的托管 bean 在被其他 bean 引用時比較有用。
@CustomScoped

在定制范圍內存儲托管 bean。

定制范圍就是指可以由頁面創建者訪問的地圖??梢酝ㄟ^編程的方式控制定制范圍內的 bean 的可視性和生命周期。 value 屬性指向一個地圖。

value

從 faces-config.xml 中移除托管 bean 聲明將極大地減少 XML,但是在 JSF 2 中,通過使用注釋(如我對托管 bean 所做的一樣)或是約定(比如 JSF 2 的簡化的導航處理),幾乎可以去掉所有的 XML 內容。





回頁首


技巧 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 應用程序的資源
places 應用程序的資源

對資源的惟一需求是它必須位于 resources 目錄或 resources 目錄的子目錄中。可以隨意命名 resources 目錄的子目錄。

在您的視圖代碼中,可以使用兩個 JSF 2 標記訪問資源: <h:outputScript> <h:outputStylesheet> 。這些標記可以結合用于 JSF 2 的 <h:head> <h:body> 標記,如清單 12 所示:


清單 12. 在 XHTML 中訪問資源
                    
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/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'] }"/>

清單 13 的非 EL 備選方法

無可否認,清單 13 中的語法不是很直觀。它訪問了一個 JSF 為了存儲資源而創建的地圖,因此很少需要使用這種語法。實際上,可以使用 <h:graphicImage/> 訪問圖像,而不需要使用 EL,比如: <h:graphicImage library="images" name="cloudy.gif"/>

在 EL 表達式內訪問資源的語法是 resource[' LIBRARY : NAME '] ,其中 LIBRARY NAME 對應于 <h:outputScript> <h:outputStylesheet> 標記的 library name 屬性。




回頁首


結束語

到目前為止,我僅僅觸及了 JSF 2 特性中最淺顯的內容,包括托管 bean、注釋、簡化導航和資源支持。在本系列隨后的兩篇文章中,我將探討 Facelets、JSF 2 的復合組件以及對 Ajax 的內置支持。






回頁首


下載

描述 名字 大小 下載方法 源代碼
jsf2fu1.zip 1.9MB HTTP
關于下載方法的信息


參考資料

學習

獲得產品和技術

討論


關于作者

David Geary

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 之星。

轉自:http://www.ibm.com/developerworks/cn/java/j-jsf2fu1/

JSF 2 簡介,第 1 部分: 流線化 Web 應用程序開發


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 韩毛片| 99久久久精品免费观看国产 | 色一情一欲一爱一乱 | 动漫美女撒尿 | 91伊人| 久久久鲁 | 日本人成18在线播放 | 日韩欧美色视频在线观看 | 久热视线观看免费视频 | 精品久久中文网址 | 欧美啪啪小视频 | 四虎最新永久在线精品免费 | 亚洲成年| 国产精品欧美日韩视频一区 | 欧美在线激情 | 欧美一级高清片欧美国产欧美 | 国产精品亚洲综合一区在线观看 | 国产精品久久一区一区 | 日本人成免费大片 | 91午夜精品亚洲一区二区三区 | 在线观看成人小视频 | 日本一级毛片大片免费 | 成人免费黄色小视频 | 久草最新网址 | 久久伦理| 香蕉在线视频网站 | 日韩毛片高清在线看 | 亚洲一级毛片 | 欧美亚洲免费 | 亚洲欧美精品综合中文字幕 | 777kkk亚洲综合欧美色老头 | 97综合视频 | 美女一级毛片免费不卡视频 | 亚洲韩精品欧美一区二区三区 | 四虎影视网站 | 不卡一级毛片免费高清 | 成人三级做爰在线观看男女 | 伊人精品影院一本到欧美 | 国产91成人精品亚洲精品 | 国产一级一片免费播放 | 97国产成人精品视频 |