多重類別 API

如果單一的 API 特別複雜,您可能要從多個 Java 類別實作 API。如要使不同的類別成為相同 API 的一部分,您必須:

舉例來說,下列兩個類別都屬於 tictactoe API 的一部分:

@Api(name = "tictactoe", version = "v1")
class TicTacToeA {  }

@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

API 設定是透過 @Api 註解屬性指定。但是對於同一個 API 的多個類別,@Api 需求則會擴充,而不僅在 @Api 註解中為每個類別提供相同的 nameversion 字串。實際上,如果在類別 @Api 屬性中指定的 API 設定出現「任何」差異,則後端 API 將無法運作。多重類別 API 中各類別的 @Api 屬性如果有任何差異,都會導致 API 設定變得「模稜兩可」,因而無法在 App Engine 適用的 Cloud Endpoints Frameworks 內運作。

您可以透過下列幾種方式建立明確的多重類別 API:

  • 以手動方式確認單一 API 中所有類別都有完全相同的 @Api 註解屬性。
  • 透過 Java 繼承使用註解繼承。在這個繼承中,單一 API 中的所有類別都會從一般以 @Api 註解的基本類別中,繼承相同的 API 設定。
  • 透過 @ApiReference 註解,將註解繼承用於單一 API 中的所有類別,以參照一般 @Api 註解類別中相同的 API 設定。

針對可能依類別而異的屬性使用 @ApiClass

如要使用這項功能,您必須匯入以下內容:

import com.google.api.server.spi.config.ApiClass;

雖然 @Api 註解中的所有屬性都必須與 API 內的所有類別相符,但您還可以透過 @ApiClass 註解,提供在各類別間不需要完全相同的屬性。例如:

// API methods implemented in this class allow only "clientIdA".
@Api(name = "tictactoe", version = "v1")
@ApiClass(clientIds = { "clientIdA" })
class TicTacToeA {  }

// API methods implemented in this class provide unauthenticated access.
@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

其中 TicTacToeA 會使用用戶端 ID 許可清單 (其中包含允許的用戶端 ID) 來限制存取權,而 TicTacToeB 則不會限制存取權。

@ApiClass 註解提供的所有屬性在 @Api 註解中都有相等的屬性。請注意,@Api 相等屬性為整個 API 適用的預設值。如果在 @Api 中指定的同一屬性為整個 API 適用的預設值,則類別特定的 @ApiClass 屬性會覆寫整個 API 適用的預設值。

下列範例說明類別特定 @ApiClass 相等值覆寫 @Api 屬性的情況:

// For this class "boards" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "boards")
class TicTacToeBoards {  }

// For this class "scores" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "scores")
class TicTacToeScores {  }

// For this class, the API-wide default "games" is used as the resource.
@Api(name = "tictactoe", version = "v1", resource = "games")
class TicTacToeGames {  }

註解繼承

@Api@ApiClass 註解屬性可以從其他類別繼承,個別屬性則能透過 Java 繼承@ApiReference 繼承加以覆寫

使用 Java 繼承

某個類別如果擴充其他含 @Api@ApiClass 註解的類別,其行為就像使用相同屬性進行註解一樣。例如:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA and TicTacToeB both behave as if they have the same @Api annotation as
// TicTacToeBase
class TicTacToeA extends TicTacToeBase {  }
class TicTacToeB extends TicTacToeBase {  }

註解只會透過 Java 子類別繼承,而不是透過介面實作。例如:

@Api(name = "tictactoe", version = "v1")
interface TicTacToeBase {  }
// Does *not* behave as if annotated.
class TicTacToeA implements TicTacToeBase {  }

因此,系統「不支援」任何類型的架構註解多重繼承

繼承也適用於 @ApiClass

@ApiClass(resource = "boards")
class BoardsBase {  }

// TicTacToeBoards behaves as if annotated with the @ApiClass from BoardsBase.
// Thus, the "resource" property will be "boards".
@Api(name = "tictactoe", version = "v1", resource = "scores")
class TicTacToeBoards extends BoardsBase {  }

其中 TicTacToeBoards 會從 BoardsBase 繼承 resource 屬性值 boards,進而覆寫其 @Api 註解中的 resource 屬性設定 (scores)。請記得,如有任何類別在 @Api 註解中指定資源屬性,則所有類別都必須在 @Api 註解中指定相同設定;此繼承技巧可讓您覆寫這個 @Api 屬性。

使用 @ApiReference 繼承

如要使用這項功能,您必須匯入以下內容:

import com.google.api.server.spi.config.ApiReference;

@ApiReference 註解提供了另一種指定註解繼承的方式。某個類別如果藉由 @ApiReference 來指定其他含 @Api@ApiClass 註解的類別,其行為就像使用相同屬性進行註解一樣。例如:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA behaves as if it has the same @Api annotation as TicTacToeBase
@ApiReference(TicTacToeBase.class)
class TicTacToeA {  }

如果同時使用 Java 繼承和 @ApiReference,註解只會透過 @ApiReference 註解繼承。類別上透過 Java 繼承所繼承的 @Api@ApiClass 註解都會遭到忽略。例如:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBaseA {  }
@Api(name = "tictactoe", version = "v2")
class TicTacToeBaseB {  }

// TicTacToe will behave as if annotated the same as TicTacToeBaseA, not TicTacToeBaseB.
// The value of the "version" property will be "v1".
@ApiReference(TicTacToeBaseA.class)
class TicTacToe extends TicTacToeBaseB {  }

覆寫繼承的設定

無論您是使用 Java 繼承還是 @ApiReference 來繼承設定,都能使用新的 @Api@ApiClass 註解覆寫繼承的設定。系統只會覆寫新註解中指定的設定屬性,未指定的屬性仍保留繼承值。例如:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@Api(name = "checkers")
class Checkers extends TicTacToe {  }

覆寫繼承也適用於 @ApiClass

@Api(name = "tictactoe", version = "v1")
@ApiClass(resource = "boards", clientIds = { "c1" })
class Boards {  }

// Scores will behave as if annotated with resource = "scores" and clientIds = { "c1" }
@ApiClass(resource = "scores")
class Scores {  }

在透過 @ApiReference 繼承時也適用覆寫:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@ApiReference(TicTacToe.class)
@Api(name = "checkers")
class Checkers {  }

繼承 @ApiMethod 註解

@ApiMethod 註解可以從已覆寫的方法繼承。例如:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST")
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with the @ApiMethod from TicTacToeBase.setGame.
  // Thus the "httpMethod" property will be "POST".
  @Override
  public Game setGame(Game game) {  }
}

@Api@ApiClass 註解繼承相似,如果互相覆寫的多個方法都有 @ApiMethod 註解,則可以覆寫個別屬性。例如:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST", clientIds = { "c1" })
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with httpMethod = "GET" and clientIds = { "c1"}.
  @ApiMethod(httpMethod = "GET")
  @Override
  public Game setGame(Game game) {  }
}

由於沒有 @ApiReference 註解或相等的方法,因此一律會透過 Java 繼承來繼承 @ApiMethod,而非透過 @ApiReference

繼承和優先順序規則

下表概要列出前面討論的繼承規則和優先順序。

註解/繼承 規則
@Api 所有類別必須相同。
@ApiClass 為類別指定以覆寫 @Api 屬性。
Java 繼承 類別繼承基本類別的 @Api@ApiClass
@ApiReference 類別繼承參照類別的 @Api@ApiClass
在從基本類別繼承的類別 (Java) 上使用 @ApiReference 類別繼承參照類別的 @Api@ApiClass,而「不是」從基本類別繼承。

註解繼承的常見用途

以下為繼承的常見用途範例:

用於 API 版本管理:

@Api(name = "tictactoe", version = "v1")
class TicTacToeV1 {  }
@Api(version = "v2")
class TicTacToeV2 extends TicTacToeV1 {  }

用於多重類別 API:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {}
@ApiClass(resource = "boards")
class TicTacToeBoards extends TicTacToeBase {  }
@ApiClass(resource = "scores")
class TicTacToeScores extends TicTacToeBase {  }

用於測試相同 API 的不同版本:

@Api(name = "tictactoe", version = "v1")
class TicTacToe {
  protected Foo someMethod() {
    // Do something real;
  }

  public Foo getFoo() {  }
}


@Api(version="v1test")
class TicTacToeTest extends TicTacToe {
  protected Foo someMethod() {
    // Stub out real action;
  }
}

其中 someMethod 可能會傳回預先建立的回應、避免具副作用的呼叫、略過網路或資料儲存庫要求等等。

新增類別到 web.xml

將類別加註之後,您必須將類別新增到 web.xml 檔案中。以下範例說明單一類別:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!-- Wrap the backend with Endpoints Frameworks v2. -->
    <servlet>
        <servlet-name>EndpointsServlet</servlet-name>
        <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
        <init-param>
            <param-name>services</param-name>
            <param-value>com.example.skeleton.MyApi</param-value>
        </init-param>
    </servlet>
    <!-- Route API method requests to the backend. -->
    <servlet-mapping>
        <servlet-name>EndpointsServlet</servlet-name>
        <url-pattern>/_ah/api/*</url-pattern>
    </servlet-mapping>
</web-app>

如何新增多個類別:

  1. <param-value>com.example.skeleton.MyApi</param-value> 改成您自己的 API 類別名稱。

  2. 在相同 <param-value> 欄位中,以逗號分隔方式新增每個類別,如下所示:

    <param-value>com.example-company.example-api.Hello,com.example-company.example-api.Goodbye</param-value>