ממשקי API של סיווג רב-מחלקתי

אם API מסוים מורכב במיוחד, כדאי להטמיע אותו מכמה מחלקות Java. כדי להוסיף מחלקות שונות לאותו API, צריך:

לדוגמה, שתי המחלקות הבאות הן חלק מ-API‏ tictactoe:

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

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

הגדרות ה-API מצוינות באמצעות מאפייני ההערה @Api. עם זאת, כשמדובר בכמה כיתות באותו API, הדרישות של @Api הן לא רק שיהיו אותם מחרוזות של name ו-version בהערה @Api לכל כיתה. למעשה, ה-API של ה-Backend לא יפעל אם יש הבדלים בהגדרות ה-API שצוינו במאפיינים של המחלקות @Api. כל הבדל במאפיינים @Api של מחלקות בתוצאה של API עם כמה מחלקות יוצר הגדרת API 'דו-משמעית', שלא תפעל ב-Cloud Endpoints Frameworks for App Engine.

יש כמה דרכים ליצור 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 מגביל את הגישה באמצעות רשימת היתרים של מזהי לקוחות שמכילה את מזהה הלקוח המורשה, ו-TicTacToeB לא מגביל את הגישה.

לכל המאפיינים שמופיעים בהערה @ApiClass יש מאפיין מקביל בהערה @Api. שימו לב שהמאפיין המקביל @Api פועל כברירת המחדל בכל ה-API. אם יש ברירת מחדל לכל ה-API עבור אותו מאפיין, שצוין ב-@Api, המאפיין @ApiClass הספציפי למחלקה מבטל את ברירת המחדל לכל ה-API.

בדוגמאות הבאות מוצגת דריסה של מאפייני @Api על ידי המקבילים הספציפיים למחלקה @ApiClass:

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

לכן, אין תמיכה בירושה מרובה מכל סוג שהוא של הערות framework.

קבלת הגדרות בירושה פועלת גם ב-@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 יורש את ערך המאפיין resource boards מ-BoardsBase, וכך מבטל את הגדרת המאפיין resource (scores) בהערה @Api שלו. חשוב לזכור: אם בכיתה כלשהי צוין מאפיין המשאב בהערת ה-@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. המערכת מתעלמת מאנוטציות @Api ו-@ApiClass במחלקה שעוברת בירושה דרך ירושה של Java. לדוגמה:

@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 של המחלקה שאליה הן מפנות.
שימוש ב-@ApiReference בכיתה (Java) שמוגדרת בירושה מכיתה בסיסית המאפיינים @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>