程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 設計與開發JAX-WS 2.0 Web服務

設計與開發JAX-WS 2.0 Web服務

編輯:關於JAVA

開始之前

關於本教程

在本教程中,我們將設計和開發一個訂單處理應用程序,並將其功能作為 Web 服務公開,以便各種使用者以獨立於平台的方式提交訂單信息。

目標

完成了此教程後,可以應用相關概念和知識來使用 JAX-WS 技術為應用程序開發 Web 服務。

先決條件

要成功完成此教程,應該對 Web 服務技術具有基本的了解,而且需要能較為熟練地進行 Java 編程。

系統要求

要運行此教程中的示例,需要安裝 Java Platform, Standard Edition (Java SE) 6.0。

JAX-WS 簡介

為何使用 JAX-WS?

JAX-WS 是用於簡化使用 Java 構造 Web 服務和 Web 服務客戶機的工作的技術。該技術提供了完整的 Web 服務堆棧,可減少開發和部署 Web 服務的任務。JAX-WS 支持 WS-I Basic Profile 1.1,後者可確保使用 JAX-WS 堆棧開發的 Web 服務能夠供采用 WS-I Basic Profile 標准使用任意語言開發的任意客戶機使用。JAX-WS 還包括了 Java Architecture for XML Binding (JAXB) 和 SOAP with Attachments API for Java (SAAJ)。

JAXB 提供了一種非常方便的方法來將 XML 模式映射到 Java 代碼的表示形式,從而支持數據綁定功能。JAXB 消除了將 SOAP 消息中的 XML 模式消息轉換為 Java 代碼的工作,因而不必全面了解 XML 和 SOAP 解析。JAXB 規范定義 Java 和 XML 模式之間的綁定。SAAJ 提供了標准的方法來處理 SOAP 消息中包含的 XML 附件。

而且,JAX-WS 提供了用於將傳統 Java 對象(Plain Old Java Object,POJO)類轉換為 Web 服務的 Annotation 庫,從而加速了 Web 服務的開發工作。另外,它還指定了從采用 Web 服務描述語言(Web Services Description Language,WSDL)定義的服務到實現該服務的 Java 類之間的詳細映射。采用 WSDL 定義的任意復雜類型都通過遵循 JAXB 規范定義的映射來映射為 Java 類。JAX-WS 之前與 Java Platform, Enterprise Edition (Java EE) 5 綁定。而 JAX-WS 2.0 規范是作為 Java Community Process (JCP) 的 JSR 224 開發的。

開發 Web 服務

契約優先方法與代碼優先方法

進入 JAX-WS 時代的最好方法莫過於首先開發一個 Web 服務。可以采用以下兩種方法之一開發 Web 服務:

契約優先:從 WSDL 契約著手,生成 Java 類來實現服務。

代碼優先:從 Java 類著手,使用 Annotation 來生成 WSDL 文件和 Java 接口。

契約優先 WSDL 方法需要對用於定義消息格式的 WSDL 和 XML 模式定義(XML Schema Definition,XSD)有良好的理解。如果您對 Web 服務相當陌生,最好從代碼優先方法著手,本教程中將使用此方法開發 Web 服務。

代碼優先 Web 服務開發

使用代碼優先方法時,將從實現希望作為服務公開的功能的 Java 類或類入手。在已經提供了 Java 實現且需要將實現作為服務公開的情況下,代碼優先方法尤為有用。

開發訂單處理 Web 服務

讓我們首先創建一個訂單處理 Web 服務,用於接受訂單信息、配送信息和訂購物品並最終生成確認 ID 作為響應。訂單處理服務的代碼如清單 1 中所示。這是一個虛擬實現,將在控制台輸出客戶 ID 和物品數量,然後輸出虛擬訂單 ID A1234。(可以在本文的下載部分下載完整的應用程序源代碼。)將源代碼解壓到 C 盤,會在其中創建一個名為 JAXWS-Tutorial 的文件夾。此文件夾包含對應的源代碼,如清單 1 中所示。

清單 1. 訂單處理 Web 服務實現

package com.ibm.jaxws.tutorial.service;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import com.ibm.jaxws.tutorial.service.bean.OrderBean;
//JWS annotation that specifies that the portType name of the
//Web service is "OrderProcessPort," the service name
//is "OrderProcess," and the targetNamespace used in the generated
//WSDL is "http://jawxs.ibm.tutorial/jaxws/orderprocess."
@WebService(serviceName = "OrderProcess",
     portName = "OrderProcessPort",  
     targetNamespace = "http://jawxs.ibm.tutorial/jaxws/orderprocess")

//JWS annotation that specifies the mapping of the service onto the
// SOAP message protocol. In particular, it specifies that the SOAP messages
//are document literal.
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,use=SOAPBinding.Use.LITERAL,
       parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class OrderProcessService {
   @WebMethod
   public OrderBean processOrder(OrderBean orderBean) {
     // Do processing...
     System.out.println("processOrder called for customer"
         + orderBean.getCustomer().getCustomerId());
     // Items ordered are
     if (orderBean.getOrderItems() != null) {
       System.out.println("Number of items is "
           + orderBean.getOrderItems().length);
     }
     //Process order.
     //Set the order ID.
     orderBean.setOrderId("A1234");
     return orderBean;
   }
}

OrderBean 中包含訂單信息,如清單 2 中所示。具體來說,其中包含對客戶、訂單項和配送地址對象的引用。

清單 2. 包含訂單信息的 OrderBean 類

package com.ibm.jaxws.tutorial.service.bean;
public class OrderBean {
   private Customer customer;
   private Address shippingAddress;
   private OrderItem[] orderItems;
   private String orderId;
   public Customer getCustomer() {
     return customer;
   }
   public void setCustomer(Customer customer) {
     this.customer = customer;
   }
   public String getOrderId() {
     return orderId;
   }
   public void setOrderId(String orderId) {
     this.orderId = orderId;
   }
   public Address getShippingAddress() {
     return shippingAddress;
   }
   public void setShippingAddress(Address shippingAddress) {
     this.shippingAddress = shippingAddress;
   }
   public OrderItem[] getOrderItems() {
     return orderItems;
   }
   public void setOrderItems(OrderItem[] orderItems) {
     this.orderItems = orderItems;
   }
}

開發 JAX-WS Web 服務的起點是一個使用 javax.jws.WebService Annotation 進行了標注的 Java 類。所使用的 JAX-WS Annotation 屬於 Web Services Metadata for the Java Platform 規范 (JSR-181) 的一部分。您可能已經注意到了,OrderProcessService 使用 WebService Annotation 進行了標注,而後者將類定義為了 Web 服務端點。

OrderProcessService 類(帶有 @javax.jws.WebService Annotation 的類)隱式地定義了服務端點接口(Service Endpoint Interface,SEI),用於聲明客戶機可以對服務調用的方法。除了使用 @WebMethod Annotation 標注且 exclude 元素設置為 true 的方法外,類中定義的所有公共方法都會映射到 WSDL 操作。@WebMethod Annotation 是可選的,用於對 Web 服務操作進行自定義。除了 exclude 元素外,javax.jws.WebMethod Annotation 還提供 operation name 和 action 元素,用於在 WSDL 文檔中自定義操作的 name 屬性和 SOAP action 元素。這些屬性是可選的;如果未定義,會從類名稱派生缺省值。

實現 Web 服務後,需要生成部署服務所需的所有構件,然後將 Web 服務打包為部署構件(通常為 WAR 文件),並將 WAR 文件部署到任何支持 JAX-WS 2.0 規范的兼容服務器上。通常生成的構件是提供基於服務接口將 Java 對象轉換為 XML、WSDL 文件和 XSD 模式的功能的類。

出於測試目的,Java 6 綁定了一個輕量級 Web 服務器,可以通過調用簡單的 API 調用將 Web 服務發布到該服務器上。接下來我們將了解如何使用此方法測試 Web 服務。

發布服務

生成 JAX-WS 構件

運行 wsgen 工具,以生成訂單處理 Web 服務的 JAX-WS 可移植構件。此工具將讀取 Web SEI 類,並生成 Web 服務部署和調用所需的所有構件。wsgen 工具生成需要發布的 Web 服務的 WSDL 文件和 XSD 模式。

為了生成 JAX-WS 構件,首先需要編譯服務和 Bean 源文件:

打開命令提示符,並進入到 c:\JAXWS-Tutorial目錄。

運行以下命令,以編譯 Java 文件,並將類文件放入其各自文件夾中:

javac com\ibm\jaxws\tutorial\service\*.java com\ibm\jaxws\tutorial\service\bean\*.java

運行以下命令,以生成 JAX-WS 構件:

wsgen -cp . com.ibm.jaxws.tutorial.service.OrderProcessService -wsdl

wsgen 工具提供了大量的選項,例如,其中提供了 -wsdl 選項,用於生成服務的 WSDL 和模式構件。運行此命令後,應該在 JAXWS-Tutorial 文件夾中看到生成的 OrderProcess.wsdl 和 OrderProcess_schema1.xsd,而且會看到在 com\ibm\jaxws\tutorial\service\jaxws 文件夾中創建了 JAX-WS 構件。

生成了構件後,運行以下 Web 服務發布器客戶機,以發布訂單處理 Web 服務。

從 c:\JAXWS-Tutorial 文件夾運行以下命令,以編譯 OrderWebServicePublisher:

javac com\ibm\jaxws\tutorial\service\publish\OrderWebServicePublisher.java

然後運行以下命令:

java com.ibm.jaxws.tutorial.service.publish.OrderWebServicePublisher

運行 Java 程序後,應該看到以下消息: The Web service is published at http://localhost:8080/OrderProcessWeb/orderprocess. To stop running the Web service, terminate this Java process.

這會將訂單 Web 服務發布到 http://localhost:8080/OrderProcessWeb/orderprocess。可以通過顯示訂單處理 Web 服務生成的 WSDL 來驗證 Web 服務是否在運行:

打開浏覽器,並導航到 http://localhost:8080/OrderProcessWeb/orderprocess?wsdl。

分析 OrderWebServicePublisher

在分析 WSDL 和模式構件前,讓我們分析一下 OrderWebServicePublisher 的代碼。清單 3 提供了 OrderWebServicePublisher 客戶機的源代碼。

清單 3. 用於發布訂單處理 Web 服務的代碼

package com.ibm.jaxws.tutorial.service.publish;
import javax.xml.ws.Endpoint;
import com.ibm.jaxws.tutorial.service.OrderProcessService;
public class OrderWebServicePublisher {
   public static void main(String[] args) {
     Endpoint.publish("http://localhost:8080/OrderProcessWeb/orderprocess",
         new OrderProcessService());
   }
}

通過 Endpoint.publish() 方法,可以方便地發布和測試 JAX-WS Web 服務。publish() 接受兩個參數:Web 服務的位置和 JAX-WS Web 服務實現類。publish() 方法在指定的 URL(本例中為本地主機,端口為 8080)創建輕量級 Web 服務器,並將 Web 服務部署到該位置。此輕量級 Web 服務器在 Java 虛擬機(Java Virtual Machine,JVM)中運行,可通過調用 endpoint.stop() 方法以有條件的方式終止,或終止 OrderWebServicePublisher 客戶機。

分析生成的 WSDL

要查看生成的訂單處理 Web 服務 WSDL,在浏覽器中鍵入以下 URL 位置: http://localhost:8080/OrderProcessWeb/orderprocess?wsdl.

讓我們分析 WSDL 一些重要方面的內容,並了解如何基於 JAX-WS 元數據生成 WSDL 和模式構件,首先要分析的是生成的 XSD。此內容使用 xsd:import 標記導入到 WSDL 文件中(請參見清單 4);schemaLocation 指定 XSD 的位置。

清單 4. 包含訂單處理模式定義的 WSDL 文件

<types>

   <xsd:schema>
    <xsd:import namespace="http://jawxs.ibm.tutorial/jaxws/orderprocess"
    schemaLocation="OrderProcess_schema1.xsd"/>
   </xsd:schema>
</types>

在浏覽器中打開 schemaLocation (http://localhost:8080/OrderProcessWeb/orderprocess?xsd=1),以查看模式定義在浏覽器中呈現的情況。讓我們分析一下其中的情況:模式定義最開始是 targetNamspace 和 tns 聲明,映射到在 OrderProcessService 的 @WebService Annotation 中定義的 targetNamespace http://jawxs.ibm.tutorial/jaxws/orderprocess。清單 5 給出了對應的代碼。

清單 5. 模式(Schema)命名空間聲明

<xs:schema version="1.0"
   targetNamespace="http://jawxs.ibm.tutorial/jaxws/orderprocess"
   xmlns:tns="http://jawxs.ibm.tutorial/jaxws/orderprocess"
   xmlns:xs="http://www.w3.org/2001/XMLSchema">

前面指定的 wsgen 工具命令生成兩個包裝 Bean 類,ProcessOrder 和 ProcessOrderResponse,分別包含訂單處理 Web 服務的輸入和輸出消息。將基於這些包裝 Bean 類生成以下模式元素:

processOrder 屬於 processOrder 類型,表示其中包含一個元素,且此元素的名稱為 arg0,類型為 orderBean。可以看到,在 ProcessOrder 類和 processOrder 復雜類型之間存在一對一映射。

processOrderResponse 與 processOrderResponse 類型類似,後者的定義映射到 ProcessOrderResponse 類。

讓我們仔細分析一下清單 6 中的代碼。

清單 6. processOrder 的模式聲明

<xs:element name="processOrder" type="tns:processOrder"/>
  <xs:element name="processOrderResponse" type="tns:processOrderResponse"/>
  <xs:complexType name="processOrder">
   <xs:sequence>
    <xs:element name="arg0" type="tns:orderBean" minOccurs="0"/>
   </xs:sequence>
  </xs:complexType>

清單 7 中所示的 orderBean 類型定義映射到 OrderBean 類。orderBean 類型定義包括:

一個 customer 元素,其類型為 customer。

一個 orderId,其類型為 string。

orderItems(它為數組類型,因為其將 maxOccurs 屬性指定為 unbounded),其類型為 orderItem。

shippingAddress,其類型為 address。

清單 7. processOrder 的模式聲明

<xs:complexType name="orderBean">
<xs:sequence>
<xs:element name="customer" type="tns:customer" minOccurs="0" />
      <xs:element name="orderId" type="xs:string" minOccurs="0" />
      <xs:element nillable="true" maxOccurs="unbounded" name="orderItems"
              type="tns:orderItem" minOccurs="0" />
      <xs:element name="shippingAddress" type="tns:address"
             minOccurs="0" />
</xs:sequence>
</xs:complexType

類似地,模式的其余部分 customer、orderItems 和 address 分別映射到 Customer、OrderItem 和 Address Java Bean。

分析了模式定義後,接下來讓我們回頭來看看 WSDL 中的消息定義,如清單 8 中所示。WSDL 指定消息 processOrder 和 processOrderResponse,其所屬的元素為 processOrder 和 processOrderResponse(我們已經討論了其模式定義)。portType 指定操作 processOrder,其輸入消息為 processOrder,而輸出消息為 processOrderResponse。

清單 8. WSDL 文檔中的 processOrder 消息元素

<message name="processOrder">
      <part element="tns:processOrder" name="parameters" />
  </message>
  <message name="processOrderResponse">
      <part element="tns:processOrderResponse" name="parameters" />
  </message>
  <portType name="OrderProcessService">
  <operation name="processOrder">
  <input message="tns:processOrder" />
  <output message="tns:processOrderResponse" />
  </operation>
  </portType>

接下來定義了 WSDL 綁定。此綁定將 soap:binding 樣式定義為 document,soap:body 使用 literal 標記指定操作 processOrder 的輸入和輸出消息格式。生成的 WSDL 定義映射到 @SOAPBinding Annotation(已在 OrderProcessService 類上定義,請參見清單 9)。

清單 9. WSDL 文檔的綁定信息

<binding name="OrderProcessPortBinding" type="tns:OrderProcessService">
  <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="processOrder">
  <soap:operation soapAction="" />
<input>
  <soap:body use="literal" />
  </input>
  <output>
  <soap:body use="literal" />
  </output>
  </operation>
</binding>

接下來定義 WSDL 服務。這將指定端口和對應的綁定類型,以及服務的實際位置。此位置通常為 HTTP 位置,在本例中為 http://localhost:8080/OrderProcessWeb/orderprocess。可以在清單 10 中了解到具體的情況。

清單 10. WSDL 文檔的服務信息

<service name="OrderProcess">
  <port name="OrderProcessPort" binding="tns:OrderProcessPortBinding">
  <soap:address location="http://localhost:8080/OrderProcessWeb/orderprocess" />
</port>

我們已經對生成的 WSDL 和模式構件進行了分析。清單 11 給出了一個示例 SOAP 請求消息,此消息是在 Web 服務客戶機調用 processOrder 操作時發送的。

清單 11. processOrder 操作的示例 SOAP 消息

<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1=" http://jawxs.ibm.tutorial/jaxws/orderprocess"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Body>
<ns1:processOrder>
<arg0>
<customer><customerId>A123</customerId>
<firstName>John</firstName><lastName>Smith</lastName></customer>
<orderItems><itemId>11</itemId><qty>11</qty></orderItems>
</arg0>
</ns1:processOrder>
</soapenv:Body>
</soapenv:Envelope>

創建 Web 服務客戶機

從 WSDL 創建 Web 服務客戶機

在本部分,我們將了解如何從 WSDL 創建 Web 服務客戶機。JAX-WS 提供了名為 wsimport 的工具,用於從 WSDL 生成 JAX-WS 可移植構件。生成的可移植構件通常包括以下內容:

SEI

服務(需要實現的服務實現類)

從模式類型生成的 JAXB 生成類

從 wsdl:fault 映射的異常類(如果有)

客戶機使用生成的構件調用 Web 服務。Web 服務客戶機並不需要處理任何 SOAP 格式(如創建或解析 SOAP 消息)。這將由 JAX-WS 運行時予以處理,此運行時將使用生成的構件代碼(JAXB 生成類)。Web 服務將處理 Java 代碼(JAXB 生成類),從而減少了開發 Web 服務客戶機和對 Web 服務調用操作的工作。

先使用 wsimport 工具從 OrderProcess WSDL 生成 JAX-WS 構件。然後要創建 Web 服務客戶機,後者使用生成的構件代碼調用訂單處理 Web 服務。要生成 JAX-WS 構件,賢進入到 JAXWS-Tutorial 目錄,並運行清單 12 中所示的 wsimport 命令。不過,進行操作前,請確保已經按照生成 JAX-WS 構件部分中的步驟 5 所述的方法,通過運行 OrderWebServicePublisher 發布了 Web 服務。

清單 12. 用於生成供 Web 服務客戶機使用的 JAX-WS 構件的 wsimport 命令

wsimport -keep -p com.ibm.jaxws.tutorial.service.client
   http://localhost:8080/OrderProcessWeb/orderprocess?wsdl

-keep 選項指示保留生成的文件,-p 選項指定需要在其中生成構件的包名稱。http://localhost:8080/OrderProcessWeb/orderprocess?wsdl 指定 WSDL 文件的位置。以下構件是從 OrderProcessService WSDL 生成的:

JAXB 類(Address、Customer, OrderBean 和 OrderItem):通過讀取 OrderProcessService WSDL 中定義的模式定義生成

RequestWrapper 和 ResponseWrapper 類(ProcessOrder 和 ProcessOrderResponse):包裝 document literal-wrapped 樣式類型的輸入和輸出

服務類 (OrderProcess):客戶機用於請求 Web 服務的類

服務接口 (OrderProcessService):包含著用於服務實現接口的類

接下來了解一下如何使用上面生成的構件創建 Web 服務客戶機。com\ibm\jaxws\tutorial\service\client文件夾中提供了一個示例參考代碼。Web 服務客戶機的代碼如清單 13 中所示。

清單 13. 訂單處理 Web 服務客戶機的代碼清單

package com.ibm.jaxws.tutorial.service.client;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
public class OrderClient {
   final QName qName = new QName(
       "http://jawxs.ibm.tutorial/jaxws/orderprocess", "OrderProcess");
   public static void main(String[] args) {
     if (args.length != 1) {
       System.out
           .println("Specify the URL of the OrderProcess Web Service");
       System.exit(-1);
     }
     URL url = getWSDLURL(args[0]);
     OrderClient client = new OrderClient();
     client.processOrder(url);
   }
   private static URL getWSDLURL(String urlStr) {
     URL url = null;
     try {
       url = new URL(urlStr);
     } catch (MalformedURLException e) {
       e.printStackTrace();
       throw new RuntimeException(e);
     }
     return url;
   }
   public void processOrder(URL url) {
     OrderProcess orderProcessingService = new OrderProcess(url, qName);
     System.out.println("Service is" + orderProcessingService);
     OrderBean order = populateOrder();
     OrderProcessService port = orderProcessingService.getOrderProcessPort();
     OrderBean orderResponse = port.processOrder(order);
     System.out.println("Order id is " + orderResponse.getOrderId());
   }
   private OrderBean populateOrder() {
     OrderBean order = new OrderBean();
     Customer customer = new Customer();
     customer.setCustomerId("A123");
     customer.setFirstName("John");
     customer.setLastName("Smith");
     order.setCustomer(customer);
     // Populate Order Item.
     OrderItem item = new OrderItem();
     item.setItemId("11");
     item.setQty(11);
     order.getOrderItems().add(item);
     return order;
   }
}

上面列出的 Web 服務客戶機代碼執行以下任務:

通過傳入 OrderProcess Web 服務的 WSDL URL 和服務的 QName 創建 OrderProcess 類的實例。

創建 OrderBean 的實例,並使用 populateOrder() 方法填充訂單信息。

對服務調用 getOrderProcessPort(),以檢索到服務的代理(也稱為端口)。端口實現服務所定義的接口。

調用端口的 processOrder 方法,並同時傳入在上面的第二個列表項目中創建的 OrderBean 實例。

從服務獲得 OrderBean 響應並輸出訂單 ID。

運行 Web 服務客戶機

要運行 Web 服務客戶機,請首先從 JAXWS-Tutorial 文件夾運行以下命令來編譯 Web 服務客戶機:

javac com\ibm\jaxws\tutorial\service\client\OrderClient.java

通過使用以下命令提供訂單處理 Web 服務的 WSDL URL 來運行 Web 服務客戶機:

java com.ibm.jaxws.tutorial.service.client.OrderClient http://localhost:8080/OrderProcessWeb/orderprocess?wsdl

運行 Web 服務客戶機時,會在控制台看到以下輸出(OrderWebServicePublisher 在控制台中運行):

processOrder called for customer A123

Number of items is 1

在運行 Web 服務客戶機的控制台中,會得到以下輸出:

Order id is A1234

如上面的客戶機代碼中所示,並不會處理調用 Web 服務操作時使用的任何基於 SOAP 或 XML 的格式;相反,需要處理的是輸入和輸出消息的 JAXB 生成類,並使用服務接口和服務類對象(充當 Web 服務調用的存根)。存根負責從 JAXB Annotation 創建 SOAP 請求,並將 SOAP 響應轉換回 Java 對象。

您現在已經成功地創建和發布了 Web 服務,並通過 Web 服務客戶機成功地執行了此服務。

總結

在本教程中,我們了解了如何使用代碼優先的開發方法和 JAX-WS 技術設計和開發 Web 服務。JAX-WS 是一個非常不錯的選擇,因為其中提供了完整的 Web 服務堆棧,以簡化 Web 服務的開發和部署。

本教程中開發的訂單處理 Web 服務使用 Document 樣式的 Web 服務,可確保服務使用者和服務提供者使用 XML 文檔進行通信。XML 文檔遵循定義良好的契約,而此類契約通常都是使用 XML 模式定義創建的。XML 模式格式指定服務使用者能夠調用和遵循的業務消息的契約。Document 樣式的 Web 服務應該是開發企業 Web 服務的首選方法。

本文配套源碼

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