使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务
http://www.ibm.com/developerworks/cn/web/wa-aj-tomcat/
使用 Jersey 和 Apache Tomcat 構建 RESTful Web 服務
Yi Ming Huang, 軟件工程師, IBM Yi Ming Huang 是在 China Development Lab 從事 Lotus ActiveInsight 的軟件工程師。他擅長與 Portlet/Widget 相關的 Web 開發(fā)并對 REST、OSGi 和 Spring 技術感興趣。 Dong Fei Wu, 軟件工程師, IBM Dong Fei Wu 是 IBM 中國軟件開發(fā)試驗室 IBM WebSphere Dashboard Framework 開發(fā)團隊的一名軟件工程師。他負責設計和開發(fā) WebSphere Dashboard Framework 的基本版本。 Qing Guo, 軟件工程師, IBM Qing Guo 是 IBM WebSphere Dashboard Framework 的開發(fā)主管。他在 J2EE 領域有豐富的經(jīng)驗。簡介:?Representational state transfer(REST)在 2000 年由 Roy Fielding 在博士論文中提出。但是,在 Java? 社區(qū)中,直到 2008 年 JSR 311(JAX-RS) 規(guī)范定稿后才將其標準化。第一個版本的參考實現(xiàn)甚至更晚。在本文中,我將介紹 Jersey —— JSR 311 的參考實現(xiàn),描述其必要的 API 和注釋。我還將展示如何通過在 Apache Tomcat 中集成 Jersey 從 servlet 風格的服務轉(zhuǎn)型到 RESTful 服務。
平均分 (28個評分)為本文評分
RESTful Web 服務簡介
REST 在 2000 年由 Roy Fielding 在博士論文中提出,他是 HTTP 規(guī)范 1.0 和 1.1 版的首席作者之一。
REST 中最重要的概念是資源(resources),使用全球 ID(通常使用 URI)標識。客戶端應用程序使用 HTTP 方法(GET/ POST/ PUT/ DELETE)操作資源或資源集。RESTful Web 服務是使用 HTTP 和 REST 原理實現(xiàn)的 Web 服務。通常,RESTful Web 服務應該定義以下方面:
- Web 服務的基/根 URI,比如 http://host/<appcontext>/resources。
- 支持 MIME 類型的響應數(shù)據(jù),包括 JSON/XML/ATOM 等等。
- 服務支持的操作集合(例如 POST、GET、PUT 或 DELETE)。
表 1 演示了典型 RESTful Web 服務中使用的資源 URI 和 HTTP 方法。(參考資料 提供了有關 RESTful Web 服務的更多介紹和設計考慮事項。)
表 1. RESTful Web 服務示例
| 方法/資源 | 資源集合, URI 如: http://host/<appctx>/resources | 成員資源,URI 如: http://host/<appctx>/resources/1234 |
| GET | 列出資源集合的所有成員。 | 檢索標識為 1234 的資源的表示形式。 |
| PUT | 使用一個集合更新(替換)另一個集合。 | 更新標記為 1234 的數(shù)字資源。 |
| POST | 在集合中創(chuàng)建數(shù)字資源,其 ID 是自動分配的。 | 在下面創(chuàng)建一個子資源。 |
| DELETE | 刪除整個資源集合。 | 刪除標記為 1234 的數(shù)字資源。 |
回頁首
JSR 311 (JAX-RS) 和 Jersey
JSR 311 或 JAX-RS(用于 RESTful Web Services 的 Java API)的提議開始于 2007 年,1.0 版本到 2008 年 10 月定稿。目前,JSR 311 版本 1.1 還處于草案階段。該 JSR 的目的是提供一組 API 以簡化 REST 樣式的 Web 服務的開發(fā)。
在 JAX-RS 規(guī)范之前,已經(jīng)有 Restlet 和 RestEasy 之類的框架,可以幫助您實現(xiàn) RESTful Web 服務,但是它們不夠直觀。Jersey 是 JAX-RS 的參考實現(xiàn),它包含三個主要部分。
- 核心服務器(Core Server):通過提供 JSR 311 中標準化的注釋和 API 標準化,您可以用直觀的方式開發(fā) RESTful Web 服務。
- 核心客戶端(Core Client):Jersey 客戶端 API 幫助您與 REST 服務輕松通信。
- 集成(Integration):Jersey 還提供可以輕松集成 Spring、Guice、Apache Abdera 的庫。
在本文的以下部分,我介紹了所有這些組件,但是更關注核心服務器。
回頁首
構建 RESTful Web 服務
我將從可以集成到 Tomcat 的 “hello world” 應用程序開始。該應用程序?qū)ьI您完成設置環(huán)境的過程,并涉及 Jersey 和 JAX-RS 的基礎知識。
然后,我將介紹更加復雜的應用程序,深入探討 JAX-RS 的本質(zhì)和特性,比如多個 MIME 類型表示形式支持、JAXB 支持等。我將從樣例中摘取一些代碼片段來介紹重要的概念。
Hello World:第一個 Jersey Web 項目
要設置開發(fā)環(huán)境,您需要以下內(nèi)容(見 參考資料 中的下載):
- IDE:Eclipse IDE for JEE (v3.4+) 或 IBM Rational Application Developer 7.5
- Java SE5 或更高版本
- Web 容器:Apache Tomcat 6.0(Jetty 和其他也可以)
- Jersey 庫:Jersey 1.0.3 歸檔,包含所有必需的庫
設置 Jersey 的環(huán)境
首先,為 Eclipse 上的 Tomcat 6.0 創(chuàng)建服務器運行時。這是用于 RESTful Web 應用程序的 Web 容器。然后創(chuàng)建一個名為 “Jersey” 應用程序,并將目標運行時指定為 Tomcat 6.0。
最后,從 Jersey 開發(fā)包中將以下庫復制到 WEB-INF 下的庫目錄:
- 核心服務器:jersey-core.jar,jersey-server.jar,jsr311-api.jar,asm.jar
- 核心客戶端:(用于測試)jersey-client.jar
- JAXB 支持:(在高級樣例中使用)jaxb-impl.jar,jaxb-api.jar,activation.jar,stax-api.jar,wstx-asl.jar
- JSON 支持:(在高級樣例中使用)jersey-json.jar
開發(fā) REST 服務
現(xiàn)在,您已經(jīng)設置好了開發(fā)第一個 REST 服務的環(huán)境,該服務對客戶端發(fā)出 “Hello”。
要做到這一點,您需要將所有的 REST 請求發(fā)送到 Jersey 容器 —— 在應用程序的 web.xml 文件中定義 servlet 調(diào)度程序(參見清單 1)。除了聲明 Jersey servlet 外,它還定義一個初始化參數(shù),指示包含資源的 Java 包。
清單 1. 在 web.xml 文件中定義 Jersey servlet 調(diào)度程度
| <servlet><servlet-name>Jersey REST Service</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class><init-param><param-name>com.sun.jersey.config.property.packages</param-name><param-value>sample.hello.resources</param-value></init-param><load-on-startup>1</load-on-startup> </servlet> <servlet-mapping><servlet-name>Jersey REST Service</servlet-name><url-pattern>/rest/*</url-pattern> </servlet-mapping> |
現(xiàn)在您將編寫一個名為 HelloResource 的資源,它接受 HTTP GET 并響應 “Hello Jersey”。
清單 2. sample.hello.resources 包中的 HelloResource
| @Path("/hello") public class HelloResource {@GET@Produces(MediaType.TEXT_PLAIN)public String sayHello() {return "Hello Jersey";} } |
該代碼中有幾個地方需要強調(diào):
- 資源類(Resource Class):注意,資源類是一個簡單的 Java 對象 (POJO),可以實現(xiàn)任何接口。這增加了許多好處,比如可重用性和簡單。
- 注釋(Annotation):在 javax.ws.rs.* 中定義,是 JAX-RS (JSR 311) 規(guī)范的一部分。
- @Path:定義資源基 URI。由上下文根和主機名組成,資源標識符類似于 http://localhost:8080/Jersey/rest/hello。
- @GET:這意味著以下方法可以響應 HTTP GET 方法。
- @Produces:以純文本方式定義響應內(nèi)容 MIME 類型。
測試 Hello 應用程序
要測試應用程序,可以打開您的瀏覽器并輸入 URL http://<host>:<port>/<appctx>/rest/hello。您將看到響應 “Hello Jersey”。這非常簡單,使用注釋處理請求、響應和方法。
以下部分將涉及 JAX-RS 規(guī)范的必要部分,使用 Contacts 示例應用程序中的代碼片段進行介紹。您可以在源代碼包中找到這個高級樣例的所有代碼(參見 下載)。
回頁首
資源
資源是組成 RESTful Web 服務的關鍵部分。您可以使用 HTTP 方法(如 GET、POST、PUT 和 DELETE)操作資源。應用程序中的所有內(nèi)容都是資源:員工、聯(lián)系人、組織等。在 JAX-RX 中,資源通過 POJO 實現(xiàn),使用 @Path 注釋組成其標識符。資源可以有子資源。在這種情況下,父資源是資源集合,子資源是成員資源。
在樣例 Contacts 應用程序中,您將操作個人聯(lián)系人和聯(lián)系人集合。ContactsResource 是 /contacts URI 組成的集合資源,ContactResource 是 /contacts/{contactId} URI 組成的成員資源。下劃線 JavaBean 是一個簡單的 Contact 類,使用 id、名稱和地址作為成員字段。參見清單 3 和清單 4 了解詳情。您還可以從本文最后下載完整的代碼包(參見 下載)。
清單 3. ContactsResource
| @Path("/contacts") public class ContactsResource {@ContextUriInfo uriInfo;@ContextRequest request;@GET@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})public List<Contact> getContacts() {List<Contact> contacts = >new ArrayList<Contact>();contacts.addAll( ContactStore.getStore().values() );return contacts;}@Path("{contact}")public ContactResource getContact(@PathParam("contact") String contact) {return new ContactResource(uriInfo, request, contact);} } |
有幾個有趣的地方需要注意。
- @Context: 使用該注釋注入上下文對象,比如 Request、Response、UriInfo、ServletContext 等。
- @Path("{contact}"):這是 @Path 注釋,與根路徑 “/contacts” 結(jié)合形成子資源的 URI。
- @PathParam("contact"):該注釋將參數(shù)注入方法參數(shù)的路徑,在本例中就是聯(lián)系人 id。其他可用的注釋有 @FormParam、@QueryParam 等。
- @Produces:響應支持多個 MIME 類型。在本例和上一個示例中,APPLICATION/XML 將是默認的 MIME 類型。
您也許還注意到了,GET 方法返回定制 Java 對象而不是 String(純文本),正如上一個 Hello World 示例所示。 JAX-RS 規(guī)范要求實現(xiàn)支持多個表示形式類型,比如 InputStream、byte[]、JAXB 元素、JAXB 元素集合等等,以及將其序列化為 XML、JSON 或純文本作為響應的能力。下文我將提供更多有關表示形式技術的信息,尤其是 JAXB 元素表示形式。
清單 4. ContactResource
| public class ContactResource {@ContextUriInfo uriInfo;@ContextRequest request;String contact;public ContactResource(UriInfo uriInfo, Request request, String contact) {this.uriInfo = uriInfo;this.request = request;this.contact = contact;}@GET@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})public Contact getContact() {Contact cont = ContactStore.getStore().get(contact);if(cont==null)throw new NotFoundException("No such Contact.");return cont;} } |
ContactResource 的代碼簡單明了。注意以下內(nèi)容:
- Representation Type Contact:Contact 是一個簡單的 JavaBean,由 @XmlRootElement 注釋,這使它可以表示為 XML 或 JSON。
- ContactStore:這是基于 HashMap 的內(nèi)存數(shù)據(jù)存儲庫,其實現(xiàn)對于本文不重要。
回頁首
方法
HTTP 方法映射到資源的 CRUD(創(chuàng)建、讀取、更新和刪除) 操作。盡管您可以做一些小修改,比如讓 PUT 方法變成創(chuàng)建或更新,但基本的模式如下:
- HTTP GET:獲取/列出/檢索單個資源或資源集合。
- HTTP POST:新建資源。
- HTTP PUT:更新現(xiàn)有資源或資源集合。
- HTTP DELETE:刪除資源或資源集合。
因為我已經(jīng)介紹過 GET 方法,我將從 POST 開始說明。就像其他方法一樣,我仍然使用 Contact 示例進行說明。
POST
通常通過填寫表單創(chuàng)建新聯(lián)系人。也就是說,HTML 表單將 POST 到服務器,服務器創(chuàng)建并維護新創(chuàng)建的聯(lián)系人。清單 5 演示了該操作的服務器端邏輯。
清單 5. 接受表單提交(POST)并新建一個聯(lián)系人
| @POST @Produces(MediaType.TEXT_HTML) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void newContact(@FormParam("id") String id,@FormParam("name") String name,@Context HttpServletResponse servletResponse ) throws IOException {Contact c = new Contact(id,name,new ArrayList<Address>());ContactStore.getStore().put(id, c);URI uri = uriInfo.getAbsolutePathBuilder().path(id).build();Response.created(uri).build();servletResponse.sendRedirect("../pages/new_contact.html"); } |
注意該示例的以下部分:
- @Consumes:聲明該方法使用 HTML FORM。
- @FormParam:注入該方法的 HTML 屬性確定的表單輸入。
- @Response.created(uri).build(): 構建新的 URI 用于新創(chuàng)建的聯(lián)系人(/contacts/{id})并設置響應代碼(201/created)。您可以使用 http://localhost:8080/Jersey/rest/contacts/<id> 訪問新聯(lián)系人。
PUT
我使用 PUT 方法更新現(xiàn)有資源。但是,也可以通過更新實現(xiàn),或者像清單 6 中的代碼片段展示的那樣創(chuàng)建一個資源。
清單 6. 接受 PUT 請求并創(chuàng)建或更新聯(lián)系人
| @PUT @Consumes(MediaType.APPLICATION_XML) public Response putContact(JAXBElement<Contact> jaxbContact) {Contact c = jaxbContact.getValue();return putAndGetResponse(c); }private Response putAndGetResponse(Contact c) {Response res;if(ContactStore.getStore().containsKey(c.getId())) {res = Response.noContent().build();} else {res = Response.created(uriInfo.getAbsolutePath()).build();}ContactStore.getStore().put(c.getId(), c);return res; } |
我還在本示例中包含了許多不同的概念,重點強調(diào)以下概念:
- Consume XML:putContact() 方法接受 APPLICATION/XML 請求類型,而這種輸入 XML 將使用 JAXB 綁定到 Contact 對象。您將在下一節(jié)中找到客戶端代碼。
- 空響應帶有不同的狀態(tài)碼:PUT 請求的響應沒有任何內(nèi)容,但是有不同的狀態(tài)碼。如果數(shù)據(jù)存儲庫中存在聯(lián)系人,我將更新該聯(lián)系人并返回 204/no content。如果沒有新聯(lián)系人,我將創(chuàng)建一個并返回 201/created。
DELETE
實現(xiàn) DELETE 方法非常簡單。示例請查看清單 7。
清單 7. 刪除其 ID 確定的聯(lián)系人
| @DELETE public void deleteContact() {Contact c = ContactStore.getStore().remove(contact);if(c==null)throw new NotFoundException("No such Contact."); } |
表示形式
在上一節(jié)中,我介紹了幾個表示形式類型。現(xiàn)在我將簡要瀏覽一遍并深入探討 JAXB 表示形式。其他受支持的表示形式有 byte[]、InputStream、File 等。
- String:純文本。
- Response:一般 HTTP 響應,包含帶有不同響應代碼的定制內(nèi)容。
- Void:帶有 204/no content 狀態(tài)碼的空響應。
- Resource Class:將流程委托給該資源類。
- POJO:使用 @XmlRootElement 注釋的 JavaBean,這讓它成為一個 JAXB bean,可以綁定到 XML。
- POJO 集合:JAXB bean 集合。
JAX-RS 支持使用 JAXB (Java API for XML Binding) 將 JavaBean 綁定到 XML 或 JSON,反之亦然。JavaBean 必須使用 @XmlRootElement 注釋。清單 8 使用 Contact bean 作為示例。沒有明確 @XmlElement 注釋的字段將包含一個名稱與之相同的 XML 元素。清單 9 顯示了用于一個 Contact bean 的序列化 XML 和 JSON 表示形式。聯(lián)系人集合的表示形式與此相同,默認使用 <Contacts> 作為包裝器元素。
清單 8. Contact bean
| @XmlRootElement public class Contact {private String id;private String name;private List<Address> addresses;public Contact() {}public Contact(String id, String name, List<Address> addresses) {this.id = id;this.name = name;this.addresses = addresses;}@XmlElement(name="address")public List<Address> getAddresses() {return addresses;}public void setAddresses(List<Address> addresses) {this.addresses = addresses;}// Omit other getters and setters } |
清單 9. 一個 Contact 的表示形式
| XML representation: <contact><address><city>Shanghai</city><street>Long Hua Street</street></address><address><city>Shanghai</city><street>Dong Quan Street</street></address><id>huangyim</id><name>Huang Yi Ming</name> </contact>JSON representation: {"contact":[{"address":[{"city":"Shanghai","street":"LongHua Street"},{"city":"Shanghai","street":"Dong QuanStreet"}],"id":"huangyim","name":"Huang Yi Ming"}]} |
對于使用 JAXB 的更高主題,請查看 參考資料 中的項目主頁。
回頁首
與 REST 服務通訊的客戶端
在目前為止的示例中,我開發(fā)了一個支持 CRUD 的 RESTful Web 服務。現(xiàn)在我開始解釋如何使用 curl 和 Jersey 客戶端 API 與該 REST 服務通訊。這樣一來,我可以測試服務器端代碼,并介紹更多有關客戶端技術的信息。
使用 curl 與 REST 服務通訊
Curl 是一個流行的命令行工具,可以向使用 HTTP 和 HTTPS 協(xié)議的服務器發(fā)送請求。這是一個與 RESTful Web 服務通訊的好工具,因為它可以通過任何 HTTP 方法發(fā)送內(nèi)容。Curl 已經(jīng)在 Linux 和 Mac 中自帶了,并且有一個實用工具,可以在 Windows? 平臺上進行安裝(見 參考資料)。
現(xiàn)在,我們初始化獲取所有聯(lián)系人的第一個 curl 命令。您可以參考 清單 3 獲取服務器端代碼。
curl http://localhost:8080/Jersey/rest/contacts
響應將使用 XML 并包含所有聯(lián)系人。
注意,getContacts() 方法還生成一個 application/json MIME 類型響應。您還可以請求該類型的內(nèi)容。
curl –HAccept:application/json http://localhost:8080/Jersey/rest/contacts
響應將是一個包含所有聯(lián)系人的 JSON 字符串。
現(xiàn)在,我將 PUT 一個新的聯(lián)系人。注意,清單 6 中的 putContact() 方法接受 XML 并使用 JAXB 將 XML 綁定到 Contact 對象。
| curl -X PUT -HContent-type:application/xml --data "<contact><id>foo</id><name>bar</name></contact>" http://localhost:8080/Jersey/rest/contacts/foo |
一個通過 “foo” 識別的新聯(lián)系人將添加到聯(lián)系人存儲庫。您可以使用 URI /contacts 或 /contacts/foo 驗證聯(lián)系人集合或單個聯(lián)系人。
使用 Jersey Client 與 REST 服務通訊
Jersey 還提供了一個客戶端庫,幫助您與服務器通訊并對 RESTful 服務進行單元測試。該庫是一個一般實現(xiàn),可以整合任何 HTTP/HTTPS-based Web 服務。
客戶端的核心類是 WebResource 類。您可以使用該類根據(jù)根 URI 構建一個請求 URL,然后發(fā)送請求并獲取響應。清單 10 展示了如何創(chuàng)建 WebResource 實例。注意 WebResource 是一個大對象,因此只創(chuàng)建一次。
清單 10. 創(chuàng)建 WebResource 實例
| Client c = Client.create(); WebResource r=c.resource("http://localhost:8080/Jersey/rest/contacts"); |
第一個 Jersey 客戶端示例將發(fā)送 GET 請求獲取所有聯(lián)系人并打印響應狀態(tài)碼和響應內(nèi)容,參見清單 11。
清單 11. GET 所有聯(lián)系人并打印響應
| ClientResponse response = r.get(ClientResponse.class); System.out.println( response.getStatus() ); System.out.println( response.getHeaders().get("Content-Type") ); String entity = response.getEntity(String.class); System.out.println(entity); |
清單 12 展示了另一個創(chuàng)建通過 “foo” 識別的新聯(lián)系人的示例。
清單 12. 創(chuàng)建一個聯(lián)系人
| Address[] addrs = {new Address("Shanghai", "Ke Yuan Street") }; Contact c = new Contact("foo", "Foo Bar", Arrays.asList(addrs));ClientResponse response = r.path(c.getId()).accept(MediaType.APPLICATION_XML).put(ClientResponse.class, c); System.out.println(response.getStatus()); |
注意 WebResource 實例的 API。它構建 URI,設置請求頭,并在一行代碼中調(diào)用請求。內(nèi)容(Contact 對象)將自動綁定到 XML。
清單 13 展示了檢索通過 “foo” 識別的聯(lián)系人(已上一個示例中創(chuàng)建)的最后一個示例然后刪除該聯(lián)系人。
清單 13. 檢索 “foo” 聯(lián)系人并刪除
| GenericType<JAXBElement<Contact>> generic = new GenericType<JAXBElement<Contact>>() {}; JAXBElement<Contact> jaxbContact = r.path("foo").type(MediaType.APPLICATION_XML).get(generic); Contact contact = jaxbContact.getValue(); System.out.println(contact.getId() + ": " + contact.getName());ClientResponse response = r.path("foo").delete(ClientResponse.class); System.out.println(response.getStatus()); |
注意,當您想獲取 JAXB bean 響應時,您需要使用 Java 2 Platform, Standard Edition (J2SE) 中引入的范型特性。
使用 Jersey 客戶端練習這些示例。您可以在資源包中找到更多樣例代碼(見 下載)。還可以參考 Jersey 網(wǎng)站查看更多信息(見 參考資料)。
回頁首
結(jié)束語
Jersey 可以使用 Jersey 集成庫與其他框架或?qū)嵱霉ぞ邘旒伞D壳?#xff0c;Jersey 可以集成 Spring、Guice,還支持 ATOM 表示形式與 apache-adbera 的集成。在 Jersey 項目主頁可以找到 API 和入門指南。
回頁首
下載
| 源代碼 | Jersey.Sample.Contact.Src.zip | 10KB | HTTP |
關于下載方法的信息
參考資料
學習
- 在 Wikipedia 上查找有關 REST 的介紹和其他相關鏈接。
- 查看 Rational Application Developer 試用版。
- 閱讀有關 Jersey client API 的內(nèi)容以獲取更多信息。
- 可以從 Java Community Process 獲取更多有關 JAX-RS (JSR 311) 的信息。
- JAXB Reference Implementation Project 提供了更多有關 JAXB 的信息。
- developerWorks 技術活動和網(wǎng)絡廣播:隨時關注 developerWorks 技術活動和網(wǎng)絡廣播。
- developerWorks Web development 專區(qū):通過專門關于 Web 技術的文章和教程,擴展您在網(wǎng)站開發(fā)方面的技能。
獲得產(chǎn)品和技術
- 從 項目網(wǎng)站 下載 Eclipse。
- 從 公司網(wǎng)站 獲取 Java SE 5.0。
- 從 項目網(wǎng)站 下載 Apache Tomcat。
- 可以在 Jersey 項目主頁 上查找下載、樣例代碼歸檔、用戶指南和 JAX-RS API 文檔。
- 獲取 curl for Windows。
作者簡介
Yi Ming Huang 是在 China Development Lab 從事 Lotus ActiveInsight 的軟件工程師。他擅長與 Portlet/Widget 相關的 Web 開發(fā)并對 REST、OSGi 和 Spring 技術感興趣。
Dong Fei Wu 是 IBM 中國軟件開發(fā)試驗室 IBM WebSphere Dashboard Framework 開發(fā)團隊的一名軟件工程師。他負責設計和開發(fā) WebSphere Dashboard Framework 的基本版本。
Qing Guo 是 IBM WebSphere Dashboard Framework 的開發(fā)主管。他在 J2EE 領域有豐富的經(jīng)驗。
?
總結(jié)
以上是生活随笔為你收集整理的使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Office教程
- 下一篇: 子类能否访问或覆盖父类的private方