多类 API

如果某一个 API 特别复杂,您可以通过多个 Java 类来实现它。如需让不同的类属于同一个 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 的白名单来限制访问权限,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 { … }

其中 TicTacToeBoardsBoardsBase 继承 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 始终通过 Java 继承方式继承,而不是通过 @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>