程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> WebSphere >> 將Spring和OpenJPA與WebSphere Application Server一起使用

將Spring和OpenJPA與WebSphere Application Server一起使用

編輯:WebSphere

引言

Spring 是一個簡化 J2EE 開發的 Java™ 框架。它具有用於 J2EE 應用程序的所有各層的功能。它還不強制要求特定的編程模型,因此與運行時環境無關,意味著可以在 Java SE 環境以外的其他應用程序服務器中使用它。Spring 在近年來的流行也許可以(至少是部分地)歸功於這些設計原則。有些 Spring 支持者甚至將該框架視為 J2EE 的替代者。在我們看來,使用 J2EE 並不排除使用 Spring 的可能性,反之亦然;相反,這些技術組件相當完美地互為補充。

OpenJPA 是一個 Java Persistence API (JPA) 實現,其根源可追溯到 SolarMetric Kodo Java Data Objects (JDO) 實現。Kodo 被 BEA 收購,後者對 Kodo 進行了擴展以實現 Java Persistence API,並最終將該代碼庫發展為開放源代碼的 Apache OpenJPA。通過 BEA 和 IBM 以及其他各方對該項目的不懈努力,當前的 OpenJPA 已成為一個用於 Java 的可行的對象-關系映射工具。

示例應用程序

本文使用一個名為 Events 的基本 Web 應用程序,以演示各種使用 Spring 和 OpenJPA 來開發運行於 WebSphere Application Server 上的應用程序的技術。我們設計了一個簡單的應用程序,以重點演示如何結合使用這些技術。該示例提供了三個簡單的用例:添加事件、列出事件和編輯事件。用戶的信息輸入保存在關系數據庫中。用戶可以查看系統中存儲的事件的列表(請參見圖 1)。對於每個事件,事件列表顯示了內部標識符以及事件標題。

圖 1. 列出事件

用戶可以通過單擊列表視圖中的 Add 按鈕添加事件。添加事件時,用戶需要填寫標題、類別、開始時間和持續時間字段。必填字段使用星號來標記(請參見圖 2)。添加事件時,Id 字段為非活動的。

用戶還可以使用列表視圖中的對應 Edit 鏈接修改事件數據。

圖 2. 添加事件

體系結構概述

Events 應用程序使用 Java SE 5 和 J2EE 1.4,並運行於 WebSphere Application Server 6.1 上。它還使用 JavaServer Faces (JSF) 1.1 技術來實現用戶界面。我們在整個應用程序體系結構中利用了 Spring Framework,並在數據訪問層使用了 Java Persistence API。

圖 3 顯示了該應用程序的稍微有點簡化的結構視圖。JSF 頁面使用 JSF 托管 Bean 來執行諸如加載和存儲事件信息等操作。為了改進可用性,存在兩種類型的托管 Bean:執行操作的 Bean (EventBacking) 和包含狀態的 Bean (EventSession)。操作(或支持)Bean 界定在請求范圍內,而包含狀態的 Bean 則界定在會話范圍內。如果您的應用程序中碰巧具有大量的視圖,則這種分離使得跨不同的視圖重用兩種類型的 Bean 變得更加容易。支持 Bean 獲得當前用戶會話狀態的句柄,因為該狀態通過 JSF 托管 Bean 功能注入到了支持 Bean 中。

圖 3. 體系結構關系圖

支持 Bean 將添加事件請求處理工作委托給無狀態會話 Bean EventServiceEJB,並將列表和保存事件委托給一個 EventService 實現。通常,您將在 Web 或 EJB 應用程序層中訪問數據庫數據,但不會同時在這兩個層中進行訪問。該示例應用程序通過服務層從兩個層訪問數據庫,以演示如何在這兩個層中使用 Spring。

EventServiceEJB 進一步將處理委托給一個 EventService 接口實現類。該服務實現類然後使用一個數據訪問對象(Data Access Object,DAO)實現類與持久數據存儲通信。服務層具有用於查找特定事件、創建、更新和刪除事件以及列出所有事件的方法。DAO 使用 Java Persistence API EntityManager 訪問數據庫數據(請參見圖 4)。

圖 4. 服務層關系圖

該體系結構包括多個層,其中包括服務層,其主要用途是使該體系結構模仿實際的應用程序。EventService 服務層實現的一個具體功能在於,它充當事務管理的插件點,稍後我們將會說明這一點。

該應用程序的域模型(請參見圖 5)包括單個名為 Event 的 Java 類,我們將其實現為 JPA 實體。我們將該實體映射到單個包括對應列的數據庫表。

圖 5. 域模型

該應用程序組織為四個項目:

events-ear:EAR 打包、共享庫等等。

events-ejb:業務邏輯層、EJB 會話 Bean

events-service:服務層和數據訪問

events-war:Web 層

開發

要使用 IDE 開發該示例應用程序,您需要安裝以下必備軟件:

Eclipse 3.4 for Java EE

IBM WebSphere Application Server v6.1(v6.1.0.9 或更新的 6.1 版。該示例應用程序使用 v6.1.0.17 進行了測試)

Java SE JDK 1.5(可以使用與 WebSphere Application Server 打包在一起的 JDK)

IBM DB2(DB2 UDB 8.2 或 Apache Derby v10.4.2.0)

該示例應用程序使用了 Spring Framework v2.5.5 和 Apache OpenJPA v1.2.0(有關所使用的其他 API 和版本的列表,請參見 events-ear/docs/libraries.txt 文件)。

按如下方式設置應用程序項目:

下載 events.zip 包並提取其內容

將源代碼樹導入 Eclipse。選擇 File 菜單下面的 Import 並選擇 General / Existing Projects into Workspace。選擇所提取的源代碼樹根目錄作為導入根。Import 對話框應該與圖 6 所示類似:

圖 6. 導入項目

創建名為 WAS 6.1 J2EE 的用戶庫。選擇 Window - Preferences,然後導航到 Java / Build Path / User Libraries,並單擊 New 創建新的庫。請注意,務必使用名稱 WAS 6.1 J2EE,以便自動將該庫添加到 events-ejb 和 events-war 項目構建路徑(請參見圖 7)。創建該庫以後,將 j2ee.jar 文件從 WAS 6.1 安裝添加到該庫。您將在 ${app_server_root }/lib/j2ee.jar 找到該文件(其中 app_server_root 指的是您的 Application Server 6.1 安裝根目錄)。

圖 7. 創建用戶庫

根據 events-ear/docs/libraries.txt 中的描述,下載所需的類庫並將它們放在項目樹中的正確位置。

編輯 events-ear 目錄中的 build.properties 文件。您應該設置 was-profile.root 變量值以反映您的 Application Server 6.1 安裝路徑。

構建項目 EAR 文件。打開 events-ear 下面的 build.xml Ant 構建文件。右鍵單擊 Eclipse Outline 視圖中的“dist”目標,並選擇 Run As / Ant Build。構建完成後,您將在源代碼樹根目錄下的 dist 目錄中找到 EAR 文件。

部署

構建應用程序 EAR 包以後,使用下面描述的過程在應用程序服務器中部署 events.ear。您將在項目結構的根目錄下的 dist 目錄中找到 EAR 包。

為應用程序創建數據庫模式或選擇現有的模式。

執行 events.ddl DDL 語句以創建數據庫表(在 events-service/setup 中)。

打開 WAS 控制台並設置連接到前面創建的數據庫模式的 XA 數據源。對數據源使用 JNDI 名稱 jdbc/eventDS,如圖 8 所示。

圖 8. 為 Bean 提供 JNDI 名稱

部署應用程序。在 Application Server 控制台中導航到 Applications / Enterprise Applications,並單擊 Install。部署向導隨即啟動。在提示輸入新應用程序的路徑時,選擇 events.ear 文件的路徑。

將 ejb/EventServiceEJB 資源引用映射到 ejb/EventServiceEJB,如圖 9 所示:

圖 9. 將 EJB 引用映射到 Bean

下一步,將 jdbc/eventDS 資源引用映射到 jdbc/eventDS JNDI 名稱(請參見圖 10)。

圖 10. 將資源引用映射到資源

最後,當部署向導完成時,單擊 Manage Applications 並選擇 events-ear / Manage Modules / events-war。將類加載器順序設置為 Classes loaded with application class loader first,然後單擊 OK 和Save。

圖 11. 管理 events-war 模塊

從 Enterprise Applications 列表中啟動該應用程序。應用程序啟動後,Application Status 列下面應該可以看到一個綠色的箭頭符號。

通過將浏覽器指向以下地址訪問該應用程序:
http://localhost:9080/events-war/faces/jsp/eventsList.jspx該 URL 應該反映您的 Application Server URL

使用 Spring Framework 和 OpenJPA

到目前為止,我們已從用例、開發和部署的角度介紹了該示例應用程序。下面讓我們將注意力轉向引導並使用 Spring 和 OpenJPA。在下一個部分中,我們將了解如何使用某些旨在簡化 Java 企業開發人員工作的 Spring 機制,包括對松散耦合、事務管理、異常處理、數據訪問和分發的支持。

容器實例化

Spring Framework 的基本原則之一在於,它允許開發人員聲明將由該框架在輕量級的容器中進行管理的服務對象(Spring 用語中的 Bean)以及它們之間的相互依賴關系。容器負責管理所聲明的 Bean 及其依賴項的生命周期。在對容器進行實例化時,Spring 將連接所有聲明的協作對象。由於該框架負責確保依賴對象能夠訪問它們的合作者,而不是讓依賴對象必須查找其合作者,因此 Spring 也稱為控制反轉(Inversion of Control ,IoC)容器。可以使用不同的機制聲明 Bean,其中一種機制就是 XML 配置文件。還可以使用編程方式或基於注釋的 Bean 聲明。

取決於應用程序層,實例化容器的方式稍微有所不同。在 Web 層中,只需通過將清單 1 中的 XML 片段放在 /WEB-INF/web.xml 文件中即可實例化該容器:

清單 1. Listener 類

<listener>
  <listener-class>
   org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

缺省情況下,此類會加載 /WEB-INF/applicationContext.xml 文件,其中預期包括 Spring Bean 聲明。可以根據需要自定義缺省配置文件路徑。Web 應用程序的 ServletContext 用作容器實例的綁定目標,以使得容器無需多次進行實例化即可供後續使用。

由於 EJB 中不存在像 J2EE 1.4 中的 Web 層初始化機制那樣用於初始化應用程序的標准方法,您需要在這裡以稍微不同的方式實例化 Spring 容器。Spring 包括幾個用於創建和加載容器的不同實現類。由於實例化容器的開銷非常大,我們應該避免在每次需要實例時對容器進行實例化。既然 EJB 規范沒有用於共享容器實例的適當機制,使用基於單一實例的實例化策略通常是適當的。

要使用此方法,您通常需要創建名為 beanRefContext.xml(缺省文件名)的特定於 EJB 的 Spring 引導配置文件,該配置文件又加載一組其他 Bean 配置文件。您還應該改寫 EJB 實現類中的 setSessionContext。EJB 層中的容器實例化不像在 Web 層中那樣無干擾性地工作。一個明顯的缺點就是您需要使用 Spring API 來顯式地查找 Bean。

一種類似的方法是使用 Spring 的抽象 EJB 實現支持類之一作為 EJB 實現的基類。這些方便的類使得代碼編寫人員不必實現 EJB 組件接口方法,而且還負責實例化 Spring 容器。但是,您仍然必須創建 beanRefContext.xml 並實現 setSessionContext。此方法的一個缺點是您無法使用自己的 EJB 基類。

有時,您最終會遇到 ServletContext 不可用的情形,甚至是在 Web 層中。如果您擴展第三方應用程序或框架,並且希望在代碼中使用 Spring Bean,但是該 API 沒有向擴展類傳遞上下文,可能就會發生這種情況。在此情況下,您可以按照上面針對 EJB 層描述的類似方式實例化 Spring 容器。

依賴項注入

使用 Spring,通過一種稱為依賴項注入(Dependency Injection,DI)的技術,容器將負責使得對協作對象的引用對依賴對象可用。依賴項注入與用於提供到協作對象的訪問的標准 J2EE 機制不同。在 J2EE 中,您使用 JNDI 環境命名上下文(Environment Naming Context,ENC)作為機制,以便通過命名空間使協作對象可用並獲得對協作對象的引用。通過使用 JNDI ENC,依賴對象可以顯式地請求對某個協作者的引用。

使用 Spring DI 時,程序員請求容器通過使得協作者引用對依賴對象可用,並通過使用構造函數或 setter 注入變體,從而解析依賴項。使用構造函數注入 時,容器在構造函數調用中傳遞協作者,而在使用 setter 注入時,容器在創建依賴對象後使用 mutator 方法調用傳遞引用。在這兩種情況下,您都需要聲明依賴項(例如使用 Spring 配置文件),並向依賴對象類添加對應的構造函數或 mutator 方法。

有關設計模式的經典圖書 Design Patterns:Elements of Reusable Object-Oriented Software 提倡“按接口而不是按實現來編程”的設計原則。即使您按接口編程,仍然需要在某個地方實例化實現類。可以簡單地以編程方式對其進行實例化,但是這樣的話,您的代碼將依賴具體的實現類。另一種方法是創建用於實例化實現的工廠類,但是您的代碼中仍然存在對實現類的依賴。務必注意的是,即使依賴項可能非常有限並且數量很少,源代碼級別的依賴項仍然存在。

清單 2 中的 EventService 接口實現類演示了此方法。這裡的服務實現僅具有對 EventDAO 接口而不是對 DAO 實現類的源代碼級別的依賴性:

清單 2. EventService 接口實現

public class EventServiceImpl implements EventService {
  private EventDAO eventDAO;

  public void setEventDAO(EventDAO eventDAO) {
   this.eventDAO = eventDAO;
  }
  // …
}

依賴項在 Spring 配置中聲明如下:

<bean id="eventDAO" class="events.service.EventDAOJPA"/>

<bean id="eventService" class="events.service.EventServiceImpl">
  <property name="eventDAO" ref="eventDAO"/>
</bean>

Spring 通過允許您在配置文件中聲明依賴項,然後將協作者連接到依賴對象,從而提供松散耦合。這使得將調用者與實現類完全分離成為可能,從而使您的代碼更加靈活。切換實現類現在成了一件非常簡單的事情,只需修改 Spring 配置文件即可。

異常處理

近年來,出現了有關 Java API 如何使用 Java 異常模型的批評。許多人爭論說,您作為程序員,不應該被迫處理預期在本質上很罕見的錯誤條件,以及由於系統或程序員錯誤而導致的無法合理恢復的錯誤。相反,您應該對此類條件使用未經檢查的異常,以便能夠可選地處理這些條件。這個學術流派認為,只有預期在正常操作期間發生的應用程序或用戶錯誤才應該使用檢查的異常來進行報告。隨著許多框架和 API(包括 Spring)贊成這種思維方式,這種異常處理策略已變得日益流行。

服務層支持

擁有良好設計和實現的服務層可以對應用程序的可擴展性和可靠性產生積極的影響。可以證明,對於高度可重用的服務層來說,添加新的最終用戶功能和修改現有的功能要簡單得多。

如果實現方式不當,事務劃分 會對服務層可重用性產生負面影響。這是一個挑戰,因為您可能使用服務層來實現差別非常大的用例,從而導致服務層調用者在不一樣的上下文中操作。調用者將會具有不同的事務需求,因此服務層應該允許調用者影響事務處理。

當應用程序的事務需求並不非常復雜時,編程方式的事務劃分可能非常繁瑣和容易出錯。聲明式事務劃分 允許您為軟件的事務行為聲明規則,並讓事務管理器自動執行這些規則。Spring 同時支持編程方式和聲明式的事務劃分。該示例應用程序使用清單 3 中的聲明來為服務層定義事務屬性:

清單 3. 事務屬性

<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
   <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="serviceMethods"
     expression="execution(* events.service.EventService.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>

此配置使用了 Spring 對面向方面的編程(Aspect Oriented Programming,AOP)的支持。在清單 3 中,我們定義了稱為事務顧問對象 (transaction advisor object) 的內容,並將事務管理器綁定到該顧問。我們告訴事務顧問將 REQUIRED 事務屬性應用於所有聲明的方法。REQUIRED 事務屬性的語義與 J2EE 中相同,這意味著該方法將始終在事務中執行。如果調用者在某個事務上下文中運行,則該方法將在調用者的上下文中執行。否則,它將創建新的事務上下文。

然後 aop:config 部分定義了一組方法,即我們對其應用事務聲明的 events.service.EventService 接口中的所有方法。以事務方式建議的類不必實現特殊的組件接口;可以為傳統 Java 對象(Plain Old Java Object,POJO)類指定事務屬性。為了實現這一點,Spring 使用事務代理來包裝原始服務對象。這裡需要注意的一點在於,服務對象本地調用並不經過事務代理,因此將始終在調用者的事務上下文中進行。

Spring 在運行時使用一個事務管理器接口實現來執行實際的事務劃分。可以對服務層進行配置,以根據目標環境的功能使用不同的事務管理器。例如,當您的服務層在 J2EE 應用程序服務器中運行時,可以告訴 Spring 使用應用程序服務器的事務管理器,如清單 4 所示:

清單 4. 事務管理器配置

<tx:jta-transaction-manager/>

此配置將使 Spring 自動選取您的應用程序服務器的事務管理器。在 Java SE 環境中,您可以配置 Spring 使用 JPA API 的事務劃分功能,如清單 5 所示:

清單 5. JPA 事務管理器聲明

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="emFactory"/>
</bean>

此技術使得無需任何代碼修改即可實現在 EJB 和 Web 層或 Java SE 環境中使用的服務層。在容器之外測試服務層也變得非常容易,從而極大地加速了開發周期。

可能會對服務層可重用性產生負面影響的另一個問題是您的異常處理策略。應該在整個應用程序中一致地處理異常。通常,您不應該被迫處理系統錯誤,但是仍然應該能夠在需要時處理這些錯誤。通常,諸如 JDBC 和 JPA 等數據訪問 API 僅提供有關異常條件的非常一般的信息,並且不支持有效地確定特定的問題根源。當服務層使用數據訪問層訪問存儲在某個企業信息系統中的數據時,服務層應該不允許將任何特定於信息系統的異常傳播到更高的層。

Spring 定義了一致的數據訪問異常類層次結構(請參見圖 12),您可以將其用作服務和 DAO 層異常的基礎。Spring 的數據訪問功能自動將數據訪問異常轉換為此異常類層次結構。如果需要,還可以使用自己的更加專用的異常來擴展此層次結構。正如前面提到過的,此層次結構中的異常未經檢查。

圖 12. Spring 數據訪問異常層次結構(取自 Spring 參考文檔的圖)。

DAO 支持

JPA 的一個重要概念是持久上下文。您與允許您對關系數據庫執行數據訪問操作的持久上下文交互。持久上下文負責將托管對象狀態與數據庫中存儲的實體狀態進行同步。可以通過 EntityManager 接口訪問持久上下文。

當應用程序運行時環境不支持容器托管的持久上下文時,應用程序需要顯式地管理該上下文的生命周期。這可能有點繁瑣,幸運的是,Spring 提供了這方面的幫助。清單 6 中的代碼行為 Spring 配置了一組 Bean 後處理器,這些後處理器增強了幕後的托管 Bean:

清單 6. 配置 Spring 容器

<context:annotation-config/>

除了其他功能以外,這些處理器還處理 Spring Bean 中的注釋,從而使得將 JPA 持久上下文注入 Java 類成為可能,例如類似於清單 7 所示的 DAO 實現類:

清單 7. JPA 持久上下文

@PersistenceContext
private EntityManager em;

此注釋使得 Spring 將一個事務持久上下文注入到類實例中。由於 Spring 模擬了具有 POJO 的容器托管持久上下文時的樣子,DAO 實現層就變得整潔多了。請注意,在 Java EE 5 中,您只能將持久上下文注入諸如 EJB 等托管對象,而不能注入 POJO。

還可以告訴 Spring 轉換數據訪問實現類的數據訪問異常,只需將清單 8 中的行添加到 Spring 的配置文件即可:

清單 8. 轉換數據訪問異常

<bean class="org.springframework.dao.annotation. 
    PersistenceExceptionTranslationPostProcessor"/>

此外,必須使用 @Repository 對 DAO 實現類進行注釋。

JavaServer Faces 支持

JSF 具有用於自定義 JSF EL 表達式中的頂級變量的解析的機制。Spring 附帶了一個變量解析器實現類,允許您在 JSF 表達式中引用 Spring 托管 Bean。對於每個頂級變量名稱,該類首先檢查 Spring 中是否存在具有該 ID 的 Bean。如果存在相應的 Bean,該類將把引用解析到此 Bean。否則,它將咨詢 JSF 缺省變量解析器。使用此解析器使您可以將 Spring Bean 注入 JSF 托管 Bean,或者在 JSF 頁面的 EL 表達式中引用 Spring Bean。清單 9 顯示了如何在 faces-config.xml 文件中配置變量解析器:

清單 9. 配置變量解析器

<variable-resolver>
  org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>

訪問 EJB Bean

使用 EJB 會話 Bean 會在調用者端產生相當冗長的代碼。要查找和調用實現為遠程無狀態會話 Bean 的服務方法,典型的代碼片段與清單 10 所示類似:

清單 10. 用於 EJB 會話 Bean 的冗長代碼

try {
  Context ctx = new InitialContext();
  Object homeObj = ctx.lookup("java:comp/env/ejb/EventServiceEJB");
  EventServiceEjbHome eventHome = (EventServiceEjbHome)
   PortableRemoteObject.narrow(homeObj, EventServiceEjbHome.class);
  EventServiceEjb eventService = eventHome.create();
  String msg = eventService.getGreeting("world");
} catch (NamingException e) {
  // handle exception
} catch (CreateException e) {
  // handle exception
} catch (RemoteException e) {
  // handle exception
}

查找代碼和異常處理是導致清單 10 變得冗長的主要原因。克服這些問題的典型解決方案是實現 ServiceLocator 模式,其中將查找代碼轉移到單獨的類中,服務用戶調用該類以獲取對服務實現類的引用。ServiceLocator 還可以將檢查的異常(在 Bean 查找或創建過程中引發的異常)轉換為未經檢查的異常。您仍然需要在使用 EJB 時處理 RemoteException 異常。

同樣,Spring 提供了針對此問題的極好解決方案。您可以在 Spring 配置中將 EJB Bean 指定為 Spring Bean,然後使用 Spring 的正常依賴項注入方法將它們作為協作者注入任何其他 Spring Bean。

清單 11. 將遠程無狀態會話 Bean 聲明為 Spring Bean

<jee:remote-slsb id="eventServiceEjb"
  jndi-name="java:comp/env/ejb/EventServiceEJB"
  business-interface="events.service.EventService"
  home-interface="events.ejb.EventServiceEjbHome"/>

如果目標字段類型指定了 EJB 業務接口類型,則調用者不需要關心調用某個 EJB 的任何細節。Spring 捕獲諸如 NamingException 和 RemoteException 等異常,並將它們作為未經檢查的異常重新引發,這樣您就可以自由地顯式處理這些異常。通過注入對代理對象的引用而不是注入實際的 EJB 遠程接口存根,Spring 還可以捕獲在業務方法調用過程中引發的異常。這樣,代理就可以截獲對 EJB 的調用,並根據情況轉換異常。遠程方法調用仍然使用按值調用 (call-by-value) 語義,您當然需要知道該語義。

結束語

Spring 可以簡化許多傳統 J2EE 編程挑戰,以提高您的工作效率。由於 Spring 的無干擾性設計,很容易將其引入現有的代碼庫或新應用程序。您還可以挑選要部署的功能;如果只需要其中一小部分,您不必使用整個堆棧。Spring 還可以與 WebSphere Application Server 和 OpenJPA 很好地集成。本文僅介紹了如何使用 Spring 的一小部分功能,建議讀者從下面的參考資料部分開始,繼續探索其他可能對您的項目有益的功能。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved