マルチクラス API

単一の API が特に複雑である場合、複数の Java クラスから実装することもできます。さまざまなクラスを同じ API の一部にするには、次のことが必要です。

たとえば、次の 2 つのクラスはどちらも tictactoe API の一部です。

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

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

API 構成は @Api アノテーション プロパティを通じて指定されます。ただし、同じ API に複数のクラスがある場合、@Api の要件は、各クラスの @Api アノテーションに単純に同じ nameversion の文字列を使用するだけではありません。実際、クラスの @Api プロパティで指定された API 構成に違いが 1 つでも存在する場合、バックエンド API は動作しません。マルチクラス API で各クラスの @Api プロパティに違いがあると API 構成が「あいまい」になり、App Engine 用の Cloud Endpoints Frameworks では動作しなくなります。

明確なマルチクラスの API を作成するには、いくつかの方法があります。

  • 同じ API に含まれるすべてのクラスの @Api アノテーションのプロパティが完全に同じであることを手動で確認します。
  • Java 継承を介してアノテーション継承を使用します。この継承では、同じ API に含まれるすべてのクラスは、共通の @Api アノテーションが付いている基本クラスから同じ API 構成を継承します。
  • 同じ API に含まれるすべてのクラスで @ApiReference アノテーションによるアノテーション継承を使用することによって、共通の @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 アノテーションで resource プロパティを指定している場合、すべてのクラスの @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 アノテーションや同等機能がないため、@ApiMethod は常に、@ApiReference ではなく Java 継承によって継承されます。

継承と優先順位のルール

これまでの説明を要約するために、次の表には、継承ルールと優先順位を示しています。

アノテーション / 継承 ルール
@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>