Hinweis: Callbacks werden nicht ausgelöst, wenn die App Engine-Anwendung von einer anderen Anwendung über die Remote API aufgerufen wird.
Mit einem Rückruf können Sie an verschiedenen Punkten im Persistenzprozess Code ausführen. Sie können diese Rückrufe verwenden, um eine funktionsübergreifende Logik wie beispielsweise Protokollierung, Stichproben, Dekoration, Auditing und Validierung einfach zu implementieren. Die Datastore API unterstützt derzeit Rückrufe, die vor und nach den Vorgängen put()
und delete()
ausgeführt werden.
PrePut
Wenn Sie einen PrePut
-Rückruf mit einer bestimmten Art registrieren, wird der Rückruf aufgerufen, bevor eine Entität dieser Art (oder, falls keine Art angegeben wird, bevor eine Entität bereitgestellt wird):
import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PutContext; import java.util.Date; import java.util.logging.Logger; class PrePutCallbacks { static Logger logger = Logger.getLogger("callbacks"); @PrePut(kinds = {"Customer", "Order"}) void log(PutContext context) { logger.fine("Putting " + context.getCurrentElement().getKey()); } @PrePut // Applies to all kinds void updateTimestamp(PutContext context) { context.getCurrentElement().setProperty("last_updated", new Date()); } }
Eine mit PrePut
annotierte Methode muss eine Instanzmethode sein, die void
zurückgibt, ein PutContext
als einziges Argument akzeptiert, keine geprüften Ausnahmen auslöst und zu einer Klasse gehört mit einem argumentlosen Konstruktor. Wenn ein PrePut
- Callback eine Ausnahme auslöst, werden keine weiteren Callbacks ausgeführt und der put()
- Vorgang löst die Ausnahme aus, bevor RPCs an den Datenspeicher ausgegeben werden.
PostPut
Wenn Sie einen PostPut
-Rückruf mit einer bestimmten Art registrieren, wird der Rückruf aufgerufen, nachdem eine Entität dieser Art (oder, falls keine Art angegeben wurde, nach einer beliebigen Entität) bereitgestellt wird.
import com.google.appengine.api.datastore.PostPut; import com.google.appengine.api.datastore.PutContext; import java.util.logging.Logger; class PostPutCallbacks { static Logger logger = Logger.getLogger("logger"); @PostPut(kinds = {"Customer", "Order"}) // Only applies to Customers and Orders void log(PutContext context) { logger.fine("Finished putting " + context.getCurrentElement().getKey()); } @PostPut // Applies to all kinds void collectSample(PutContext context) { Sampler.getSampler().collectSample( "put", context.getCurrentElement().getKey()); } }
Eine mit PostPut
annotierte Methode muss eine Instanzmethode sein, die void
zurückgibt, ein PutContext
als einziges Argument akzeptiert, keine geprüften Ausnahmen auslöst und zu einer Klasse gehört mit einem argumentlosen Konstruktor. Wenn ein PostPut
- Callback eine Ausnahme auslöst, werden keine weiteren Callbacks ausgeführt, aber das Ergebnis des put()
- Vorgangs ist davon nicht betroffen. Wenn der Vorgang put()
fehlschlägt, werden PostPut
Callbacks überhaupt nicht aufgerufen. Darüber hinaus werden PostPut
Callbacks, die transaktionalen put()
Operationen zugeordnet sind, erst ausgeführt, wenn die Transaktion erfolgreich committen konnte.
PreDelete
Wenn Sie einen PreDelete
-Rückruf mit einer bestimmten Art registrieren, wird der Rückruf aufgerufen, bevor eine Entität dieser Art gelöscht wird (oder, falls keine Art angegeben wird, bevor eine Entität gelöscht wird):
import com.google.appengine.api.datastore.DeleteContext; import com.google.appengine.api.datastore.PreDelete; import java.util.logging.Logger; class PreDeleteCallbacks { static Logger logger = Logger.getLogger("logger"); @PreDelete(kinds = {"Customer", "Order"}) void checkAccess(DeleteContext context) { if (!Auth.canDelete(context.getCurrentElement()) { throw new SecurityException(); } } @PreDelete // Applies to all kinds void log(DeleteContext context) { logger.fine("Deleting " + context.getCurrentElement().getKey()); } }
Eine mit PreDelete
annotierte Methode muss eine Instanzmethode sein, die void
zurückgibt, ein DeleteContext
als einziges Argument akzeptiert, keine geprüften Ausnahmen auslöst und zu einer Klasse gehört mit einem argumentlosen Konstruktor. Wenn ein PreDelete
- Callback eine Ausnahme auslöst, werden keine weiteren Callbacks ausgeführt und der delete()
- Vorgang löst die Ausnahme aus, bevor RPCs an den Datenspeicher ausgegeben werden.
PostDelete
Wenn Sie einen PostDelete
-Rückruf mit einer bestimmten Art registrieren, wird der Rückruf aufgerufen, nachdem eine Entität dieser Art gelöscht wurde (oder falls keine Entität angegeben wurde, nachdem eine Entität gelöscht wurde):
import com.google.appengine.api.datastore.DeleteContext; import com.google.appengine.api.datastore.PostDelete; import java.util.logging.Logger; class PostDeleteCallbacks { static Logger logger = Logger.getLogger("logger"); @PostDelete(kinds = {"Customer", "Order"}) void log(DeleteContext context) { logger.fine( "Finished deleting " + context.getCurrentElement().getKey()); } @PostDelete // Applies to all kinds void collectSample(DeleteContext context) { Sampler.getSampler().collectSample( "delete", context.getCurrentElement().getKey()); } }
Eine mit PostDelete
annotierte Methode muss eine Instanzmethode sein, die void
zurückgibt, ein DeleteContext
als einziges Argument akzeptiert, keine geprüften Ausnahmen auslöst und zu einer Klasse gehört mit einem argumentlosen Konstruktor. Wenn ein PostDelete
- Callback eine Ausnahme auslöst, werden keine weiteren Callbacks ausgeführt, aber das Ergebnis des delete()
- Vorgangs ist davon nicht betroffen. Wenn der Vorgang delete()
fehlschlägt, werden PostDelete
Callbacks überhaupt nicht aufgerufen. Darüber hinaus werden PostDelete
Callbacks, die transaktionalen delete()
Operationen zugeordnet sind, erst ausgeführt, wenn die Transaktion erfolgreich committen konnte.
PreGet
Sie können einen
PreGet
Callback registrieren, der aufgerufen werden soll, bevor Entitäten bestimmter Arten (oder aller Arten) abgerufen werden. Dies ist beispielsweise nützlich, um GET-Anfragen abzufangen und die Daten aus einem Cache statt aus dem Datenspeicher abzurufen.
import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.PreGetContext; import com.google.appengine.api.datastore.PreGet; class PreGetCallbacks { @PreGet(kinds = {"Customer", "Order"}) public void preGet(PreGetContext context) { Entity e = MyCache.get(context.getCurrentElement()); if (e != null) { context.setResultForCurrentElement(e); } // if not in cache, don't set result; let the normal datastore-fetch happen } }
PreQuery
Sie können einen
PreQuery
- Callback registrieren, der aufgerufen werden soll, bevor Abfragen für bestimmte Arten (oder alle Arten) ausgeführt werden. Dies ist beispielsweise nützlich, um einen Gleichheitsfilter basierend auf dem angemeldeten Nutzer hinzuzufügen.
Die Abfrage wird an den Rückruf übergeben. Durch eine Änderung der Abfrage kann über den Rückruf stattdessen eine andere Abfrage ausgeführt werden.
Oder der Rückruf kann eine ungeprüfte Ausnahme auslösen, um zu verhindern, dass die Abfrage ausgeführt wird.
import com.google.appengine.api.datastore.PreQueryContext; import com.google.appengine.api.datastore.PreQuery; import com.google.appengine.api.datastore.Query; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; class PreQueryCallbacks { @PreQuery(kinds = {"Customer"}) // Queries should only return data owned by the logged in user. public void preQuery(PreQueryContext context) { UserService users = UserServiceFactory.getUserService(); context.getCurrentElement().setFilter( new FilterPredicate("owner", Query.FilterOperator.EQUAL, users.getCurrentUser())); } }
PostLoad
Sie können einen
PostLoad
- Callback registrieren, der nach dem Laden von Entitäten bestimmter Arten (oder aller Arten) aufgerufen wird. Hier bedeutet "Laden" ggf. das Ergebnis einer GET-Anfrage oder einer Abfrage. Dies ist beim Dekorieren geladener Entitäten von Vorteil.
import com.google.appengine.api.datastore.PostLoadContext; import com.google.appengine.api.datastore.PostLoad; class PostLoadCallbacks { @PostLoad(kinds = {"Order"}) public void postLoad(PostLoadContext context) { context.getCurrentElement().setProperty("read_timestamp", System.currentTimeMillis()); } }
Batchvorgänge
Wenn Sie einen Batchvorgang ausführen (z. B. put()
mit mehreren Entitäten), werden Ihre Rückrufe einmal pro Entität aufgerufen. Sie können auf den gesamten Objekt-Batch zugreifen, indem Sie CallbackContext.getElements()
für das Argument Ihrer Rückrufmethode aufrufen. Auf diese Weise können Sie Rückrufe implementieren, die für den gesamten Batch und nicht nur für jeweils ein Element gelten.
import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PutContext; class Validation { @PrePut(kinds = "TicketOrder") void checkBatchSize(PutContext context) { if (context.getElements().size() > 5) { throw new IllegalArgumentException( "Cannot purchase more than 5 tickets at once."); } } }
Wenn Ihr Rückruf nur einmal pro Batch ausgeführt werden soll, verwenden Sie CallbackContext.getCurrentIndex()
, um festzustellen, ob Sie das erste Element des Batches betrachten.
import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PutContext; import java.util.logging.Logger; class Validation { static Logger logger = Logger.getLogger("logger"); @PrePut void log(PutContext context) { if (context.getCurrentIndex() == 0) { logger.fine("Putting batch of size " + getElements().size()); } } }
Asynchrone Vorgänge
Beachten Sie die folgenden wichtigen Hinweise dazu, wie Rückrufe mit asynchronen Datenspeichervorgängen interagieren. Wenn Sie ein ausführen, put()
oder a delete()
mit der asynchronen Datenspeicher-API , any Pre*
Rückrufe, die Sie registriert haben, werden synchron ausgeführt. Ihre Post*
- Rückrufe werden ebenfalls synchron ausgeführt, jedoch erst, wenn Sie eine der Future.get()
-Methoden aufrufen, um das Ergebnis des Vorgangs abzurufen.
Rückrufe mit App Engine-Diensten verwenden
Rückrufe haben wie jeder andere Code in Ihrer Anwendung Zugriff auf die gesamte Palette der Back-End-Dienste von App Engine. Sie können außerdem beliebig viele Rückrufe in einer einzigen Klasse definieren.
import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.DeleteContext; import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PostPut; import com.google.appengine.api.datastore.PostDelete; import com.google.appengine.api.datastore.PutContext; import com.google.appengine.api.memcache.MemcacheService; import com.google.appengine.api.memcache.MemcacheServiceFactory; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; class ManyCallbacks { @PrePut(kinds = {"kind1", "kind2"}) void foo(PutContext context) { MemcacheService ms = MemcacheServiceFactory.getMemcacheService(); // ... } @PrePut void bar(PutContext context) { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); // ... } @PostPut(kinds = {"kind1", "kind2"}) void baz(PutContext context) { Queue q = QueueFactory.getDefaultQueue(); // ... } @PostDelete(kinds = {"kind2"}) void yam(DeleteContext context) { URLFetchService us = URLFetchServiceFactory.getURLFetchService(); // ... } }
Häufig auftretende Fehler vermeiden
Beim Implementieren von Rückrufen gibt es eine Reihe häufig auftretender Fehler, die Sie vermeiden sollten.
- Nicht-statischen Zustand nicht beibehalten
- Ausführungsreihenfolge von Rückrufen nicht voraussetzen
- Ein Rückruf pro Methode
- Abrufen asynchroner Ergebnisse nicht vergessen
- Endlosschleifen vermeiden
Nicht-statischen Zustand nicht beibehalten
import java.util.logging.Logger; import com.google.appengine.api.datastore.PrePut; class MaintainsNonStaticState { static Logger logger = Logger.getLogger("logger"); // ERROR! // should be static to avoid assumptions about lifecycle of the instance boolean alreadyLogged = false; @PrePut void log(PutContext context) { if (!alreadyLogged) { alreadyLogged = true; logger.fine( "Finished deleting " + context.getCurrentElement().getKey()); } } }
Ausführungsreihenfolge von Rückrufen nicht voraussetzen
Pre*
- Callbacks werden immer vor Post*
- Callbacks ausgeführt. Es ist jedoch nicht sicher, Annahmen über die Reihenfolge zu treffen, in der ein Pre*
- Callback relativ zu anderen Pre*
- Callbacks ausgeführt wird machen Sie Annahmen über die Reihenfolge, in der ein Post*
- Callback relativ zu anderen Post*
- Callbacks ausgeführt wird.
import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.PrePut; import java.util.HashSet; import java.util.Set; class MakesAssumptionsAboutOrderOfCallbackExecution { static Set<Key> paymentKeys = new HashSet<Key>(); @PrePut(kinds = "Order") void prePutOrder(PutContext context) { Entity order = context.getCurrentElement(); paymentKeys.addAll((Collection<Key>) order.getProperty("payment_keys")); } @PrePut(kinds = "Payment") void prePutPayment(PutContext context) { // ERROR! // Not safe to assume prePutOrder() has already run! if (!paymentKeys.contains(context.getCurrentElement().getKey()) { throw new IllegalArgumentException("Unattached payment!"); } } }
Ein Rückruf pro Methode
Eine Klasse kann zwar eine unbegrenzte Anzahl von Rückrufmethoden haben, trotzdem kann eine einzelne Methode nur einem einzelnen Rückruf zugeordnet werden.
import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PostPut; class MultipleCallbacksOnAMethod { @PrePut @PostPut // Compiler error! void foo(PutContext context) { } }
Abrufen asynchroner Ergebnisse nicht vergessen
Post*
- Rückrufe werden erst ausgeführt, wenn Sie Future.get()
aufrufen, um das Ergebnis des Vorgangs abzurufen. Wenn Sie vergessen, Future.get()
aufzurufen, bevor Sie die HTTP-Anfrage bearbeitet haben, werden Ihre Post*
- Rückrufe nicht ausgeführt.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.PostPut; import com.google.appengine.api.datastore.PutContext; import java.util.concurrent.Future; class IgnoresAsyncResult { AsyncDatastoreService ds = DatastoreServiceFactory.getAsyncDatastoreService(); @PostPut void collectSample(PutContext context) { Sampler.getSampler().collectSample( "put", context.getCurrentElement().getKey()); } void addOrder(Entity order) { Futureresult = ds.put(order); // ERROR! Never calls result.get() so collectSample() will not run. } }
Endlosschleifen vermeiden
Wenn Sie in Ihren Rückrufen Datenspeichervorgänge durchführen, achten Sie darauf, nicht in eine Endlosschleife zu geraten.
import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.PrePut; import com.google.appengine.api.datastore.PutContext; class InfiniteLoop { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); @PrePut void audit(PutContext context) { Entity original = ds.get(context.getCurrentElement().getKey()); Entity auditEntity = new Entity(original.getKind() + "_audit"); auditEntity.setPropertiesFrom(original); // INFINITE LOOP! // Restricting the callback to specific kinds would solve this. ds.put(auditEntity); } }
Rückrufe mit Eclipse verwenden
Wenn Sie Ihre Anwendung mit Eclipse entwickeln, müssen Sie ein paar Konfigurationsschritte durchführen, um Datenspeicherrückrufe zu verwenden. Diese Schritte gelten für Eclipse 3.7. Diese Schritte werden in einer zukünftigen Version des Google-Plug-ins für Eclipse wahrscheinlich nicht mehr erforderlich sein.
- Öffnen Sie für Ihr Projekt das Dialogfeld "Eigenschaften" (Projekt > Eigenschaften).
- Öffnen Sie das Dialogfeld "Annotationsverarbeitung" (Java-Compiler > Annotationsverarbeitung).
- Klicken Sie auf das Kästchen "Annotationsverarbeitung aktivieren".
- Klicken Sie auf das Kästchen "Verarbeitung im Editor aktivieren".
- Öffnen Sie das Dialogfeld "Factory-Pfad" (Java-Compiler > Annotationsverarbeitung > Factory-Pfad)
- Klicken Sie auf "Externe JARs hinzufügen".
- Wählen Sie <SDK_ROOT>/lib/impl/appengine-api.jar aus (wobei SDK_ROOT das oberste Verzeichnis Ihrer SDK-Installation ist).
- Klicken Sie auf "OK".
Sie können überprüfen, ob Rückrufe richtig konfiguriert sind, indem Sie eine Methode mit mehreren Rückrufen implementieren (siehe Code-Snippet unter Ein Rückruf pro Methode). Dies sollte einen Compilerfehler erzeugen.