API multiclase

Si una API es compleja, es posible que desees implementarla desde varias clases de Java. Para que diferentes clases formen parte de la misma API, realiza lo siguiente:

Por ejemplo, las dos clases siguientes son parte de la API de tictactoe:

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

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

La configuración de la API se especifica a través de las propiedades de la anotación @Api. Sin embargo, para varias clases en la misma API, los requisitos de @Api no se tratan solo de tener las mismas strings de name y version en la anotación @Api de cada clase. No puede haber ninguna diferencia en la configuración de la API especificada en las propiedades de @Api de la clase, de lo contrario, tu API de backend no funcionará. Las diferencias en las propiedades de @Api para las clases de una API multiclase generan una configuración de API “ambigua”, que no funcionará en Cloud Endpoints Frameworks para App Engine.

Existen varias maneras de crear una API multiclase que no sea ambigua:

  • De forma manual, asegúrate de que todas las clases en una sola API tengan las mismas propiedades que la anotación @Api.
  • Usa la herencia de anotación mediante la herencia de Java. En esta herencia, todas las clases en una sola API heredan la misma configuración de API a partir de una clase en común básica anotada con @Api.
  • Usa la herencia de anotación a través de la anotación @ApiReference en todas las clases de una sola API para hacer referencia a la misma configuración de API a partir de una clase en común anotada con @Api.

Usa @ApiClass para propiedades que pueden diferir entre clases

Para usar esta característica necesitas la importación siguiente:

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

Si bien todas las propiedades en la anotación @Api deben coincidir en todas las clases de una API, también puedes usar la anotación @ApiClass para proporcionar propiedades que no deben ser idénticas entre las clases. Por ejemplo:

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

aquí TicTacToeA limita el acceso mediante una lista blanca de ID de cliente que contienen el ID de cliente permitido, y TicTacToeB no limita el acceso.

Todas las propiedades proporcionadas por la anotación @ApiClass tienen una propiedad equivalente en la anotación @Api. Ten en cuenta que la propiedad equivalente de @Api actúa como la propiedad predeterminada en toda la API. Si hay una configuración predeterminada en toda la API para esa misma propiedad, especificada en @Api, la propiedad de @ApiClass específica de la clase anula la configuración predeterminada en toda la API.

En los siguientes ejemplos, se ilustra la anulación de las propiedades de @Api por parte de las equivalentes de @ApiClass específicas de la clase:

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

Herencia de anotación

Las propiedades de anotación de @Api y @ApiClass pueden heredarse de otras clases y las propiedades individuales pueden anularse mediante la herencia de Java o la herencia de @ApiReference.

Usa la herencia de Java

Una clase que extiende otra clase con anotaciones @Api o @ApiClass se comporta como si estuviera anotada con las mismas propiedades. Por ejemplo:

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

Las anotaciones solo se heredan mediante la subclasificación de Java, no mediante la implementación de la interfaz. Por ejemplo:

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

Por lo tanto, no se admite ningún tipo de herencia múltiple de anotaciones de marcos de trabajo.

La herencia también funciona para @ApiClass, como se muestra a continuación:

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

aquí TicTacToeBoards hereda el valor boards de la propiedad resource desde BoardsBase, lo que anula la configuración de la propiedad resource (scores) en su anotación @Api. Recuerda que si alguna clase especificó la propiedad de recurso en la anotación @Api, todas las clases deben especificar esa misma configuración en la anotación @Api. Esta técnica de herencia te permite anular esa propiedad de @Api.

Usa la herencia de @ApiReference

Para usar esta característica necesitas la importación siguiente:

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

La anotación @ApiReference proporciona una forma alternativa de especificar la herencia de la anotación. Una clase que usa @ApiReference para especificar otra clase con anotaciones @Api o @ApiClass se comporta como si estuviera anotada con las mismas propiedades. Por ejemplo:

@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 se usan la herencia de Java y @ApiReference, las anotaciones se heredan solo a través de la anotación @ApiReference. Las anotaciones @Api y @ApiClass de la clase heredadas a través de la herencia de Java se ignoran. Por ejemplo:

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

Anula la configuración heredada

Ya sea que heredes la configuración mediante la herencia de Java o @ApiReference, puedes anular la configuración heredada con una nueva anotación @Api o @ApiClass. Solo se anulan las propiedades de configuración especificadas en la anotación nueva. Las propiedades que no se especifican sí se heredan. Por ejemplo:

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

La anulación de la herencia también funciona para @ApiClass, como se muestra a continuación:

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

La anulación también funciona cuando se hereda a través de @ApiReference, como se ilustra a continuación:

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

Hereda anotaciones @ApiMethod

La anotación @ApiMethod se puede heredar de métodos anulados. Por ejemplo:

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

Al igual que con la herencia de las anotaciones @Api y @ApiClass, si varios métodos que se anulan entre sí tienen anotaciones @ApiMethod, las propiedades individuales se pueden anular. Por ejemplo:

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

No hay ninguna anotación @ApiReference o equivalente para los métodos, por lo que @ApiMethod siempre se hereda a través de la herencia de Java, no a través de @ApiReference.

Reglas de herencia y precedencia

Para resumir la explicación anterior, en la tabla que aparece a continuación, se muestran las reglas de herencia y el orden de precedencia.

Anotación/herencia Regla
@Api Debe ser idéntica para todas las clases.
@ApiClass Se especifica para que una clase anule las propiedades de @Api.
Herencia de Java La clase hereda @Api y @ApiClass de la clase básica.
@ApiReference La clase hereda @Api y @ApiClass de la clase a la que se hace referencia.
Usar @ApiReference en una clase (Java) que hereda de una clase básica La clase hereda @Api y @ApiClass de la clase a la que se hace referencia, no de la clase básica.

Casos prácticos comunes de herencia de anotación

A continuación, se muestran ejemplos de los casos prácticos típicos de la herencia:

Para el control de versiones de la API:

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

Para API multiclase:

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

Para probar diferentes versiones de la misma 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;
  }
}

aquí someMethod podría mostrar respuestas predeterminadas, evitar llamadas con efectos secundarios, omitir una solicitud de red o almacén de datos y demás.

Agrega las clases a web.xml

Después de anotar tus clases, debes agregarlas a tu archivo web.xml. El ejemplo siguiente muestra una sola clase:

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

Para agregar clases múltiples, sigue estos pasos:

  1. Reemplaza <param-value>com.example.skeleton.MyApi</param-value> por tu propio nombre de clase de API.

  2. Agrega todas las clases dentro del mismo campo <param-value> separadas por una coma, como se muestra a continuación:

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