API multiclasses

Si l'une de vos API est particulièrement complexe, vous pouvez la mettre en œuvre à partir de plusieurs classes Java. Pour que différentes classes fassent partie de la même API, vous devez :

Par exemple, les deux classes suivantes font toutes les deux partie de l'API tictactoe :

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

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

La configuration de l'API est spécifiée via les propriétés de l'annotation @Api. Toutefois, dans le cas des API multiclasses, les exigences de l'annotation @Api vont au-delà du simple partage de la même valeur pour name et version dans l'annotation @Api de chaque classe. En fait, votre API backend ne fonctionnera pas s'il existe la moindre différence dans les configurations d'API spécifiées dans les propriétés @Api des classes. Toute différence au sein des propriétés @Api dans une API multiclasse entraîne une configuration "ambiguë" de l'API, qui ne fonctionnera pas dans Cloud Endpoints Frameworks pour App Engine.

Il existe plusieurs façons de créer une API multiclasse et non ambiguë :

  • Vérifiez manuellement que toutes les classes d'une API ont exactement les mêmes propriétés d'annotation @Api.
  • Utilisez l'héritage d'annotation via la fonctionnalité d'héritage Java. Ainsi, toutes les classes d'une API héritent de la configuration d'API d'une classe de base commune définie par l'annotation @Api.
  • Utilisez l'héritage d'annotation @ApiReference sur toutes les classes d'une même API afin qu'elles référencent la même configuration d'API d'une classe commune définie par l'annotation @Api.

Utiliser @ApiClass pour les propriétés pouvant différer entre des classes

Pour utiliser cette fonctionnalité, vous avez besoin de l'importation suivante :

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

Bien que toutes les propriétés de l'annotation @Api doivent correspondre pour toutes les classes d'une API, vous pouvez également utiliser l'annotation @ApiClass pour fournir des propriétés qui ne doivent pas nécessairement être identiques entre les classes. Exemple :

// 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 limite l'accès en utilisant une liste blanche d'ID clients contenant l'ID client autorisé, et où TicTacToeB ne limite pas l'accès.

Toutes les propriétés fournies par l'annotation @ApiClass ont une propriété équivalente dans l'annotation @Api. Notez que la propriété @Api équivalente sert de valeur par défaut au niveau de l'API. Si une valeur par défaut au niveau de l'API est spécifiée pour cette propriété dans @Api, la propriété @ApiClass, propre à une classe, la remplace.

Les exemples suivants illustrent le remplacement des propriétés @Api par les équivalents @ApiClass propres à une classe :

// 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 { … }

Héritage d'annotation

Les propriétés d'annotation @Api et @ApiClass peuvent être héritées d'autres classes, et des propriétés individuelles peuvent être remplacées via l'héritage Java ou l'héritage @ApiReference.

Utiliser l'héritage Java

Une classe qui étend une autre classe avec des annotations @Api ou @ApiClass se comportera comme si elle était annotée avec les mêmes propriétés. Exemple :

@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 { … }

Les annotations ne sont héritées que par la définition de sous-classes Java et non par la mise en œuvre d'une interface. Exemple :

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

Par conséquent, il n'existe aucune compatibilité avec les héritages multiples d'annotations de frameworks.

L'héritage fonctionne également pour @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 hérite de BoardsBase la valeur boards pour la propriété resource, remplaçant ainsi le paramètre de la propriété resource (scores) dans son annotation @Api. Gardez à l'esprit que si une classe a spécifié la propriété de ressource dans l'annotation @Api, toutes les classes doivent spécifier le même paramètre dans leur annotation @Api. Cette technique d'héritage vous permet de remplacer cette propriété @Api.

Utiliser l'héritage @ApiReference

Pour utiliser cette fonctionnalité, vous avez besoin de l'importation suivante :

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

L'annotation @ApiReference fournit un autre moyen de spécifier l'héritage d'annotation. Une classe qui utilise @ApiReference pour spécifier une autre classe avec des annotations @Api ou @ApiClass se comportera comme si elle était annotée avec les mêmes propriétés. Exemple :

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

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

Si l'héritage Java et @ApiReference sont utilisés conjointement, seul l'héritage @ApiReference fonctionnera. Les annotations @Api et @ApiClass sur la classe héritée via l'héritage Java sont ignorées. Exemple :

@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 { … }

Remplacer une configuration héritée

Qu'une configuration soit héritée via l'héritage Java ou via @ApiReference, vous pouvez la remplacer à l'aide d'une nouvelle annotation @Api ou @ApiClass. Seules les propriétés de configuration spécifiées dans la nouvelle annotation sont remplacées. Les propriétés non spécifiées sont toujours héritées. Exemple :

@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 { … }

Le remplacement de l'héritage fonctionne également pour @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 { … }

Le remplacement fonctionne aussi lors de l'héritage via @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 { … }

Hériter des annotations @ApiMethod

L'annotation @ApiMethod peut être héritée via des méthodes de remplacement. Exemple :

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) { … }
}

Comme pour l'héritage d'annotations @Api et @ApiClass, si plusieurs méthodes de remplacement ont des annotations @ApiMethod, des propriétés individuelles peuvent être remplacées. Exemple :

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) { … }
}

Il n'existe pas d'annotation @ApiReference ni d'équivalent pour les méthodes. Par conséquent, @ApiMethod est toujours héritée via l'héritage Java et non via @ApiReference.

Règles d'héritage et de priorité

Pour synthétiser les points abordés jusqu'ici, le tableau ci-dessous présente les règles d'héritage et l'ordre de priorité respecté.

Annotation/Héritage Règle
@Api Doit être identique pour toutes les classes.
@ApiClass Spécifiée pour qu'une classe remplace les propriétés @Api.
Héritage Java La classe hérite des propriétés @Api et @ApiClass de la classe de base.
@ApiReference La classe hérite des propriétés @Api et @ApiClass de la classe référencée.
Utilisation de @ApiReference sur une classe (Java) héritant d'une classe de base La classe hérite des propriétés @Api et @ApiClass de la classe référencée, pas de la classe de base.

Cas d'utilisation courants pour l'héritage d'annotation

Vous trouverez ci-dessous des cas d'utilisation typiques pour l'héritage :

Pour la gestion des versions d'API :

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

Pour les API multiclasses :

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

Pour tester différentes versions de la même 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 peut renvoyer des réponses prédéterminées, éviter les appels ayant des effets secondaires, ignorer une requête issue d'un réseau ou d'un datastore, etc.

Ajouter les classes à web.xml

Après avoir annoté vos classes, vous devez les ajouter à votre fichier web.xml. L'exemple suivant montre une classe unique :

<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>

Pour ajouter plusieurs classes :

  1. Remplacez <param-value>com.example.skeleton.MyApi</param-value> par le nom de votre classe d'API.

  2. Ajoutez chaque classe dans le même champ <param-value> en les séparant par une virgule, par exemple :

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