Wie werden Spring Data-Repositorys tatsächlich implementiert?

111

Ich arbeite seit einiger Zeit mit dem Spring Data JPA-Repository in meinem Projekt und kenne die folgenden Punkte:

  • In den Repository-Schnittstellen können wir die folgenden Methoden hinzufügen findByCustomerNameAndPhone()(vorausgesetzt customerNameund phonesind Felder im Domänenobjekt).
  • Anschließend stellt Spring die Implementierung bereit, indem die oben genannten Repository-Schnittstellenmethoden zur Laufzeit (während des Anwendungslaufs) implementiert werden.

Ich bin daran interessiert, wie dies codiert wurde, und habe mir den Spring JPA-Quellcode und die APIs angesehen, konnte jedoch keine Antworten auf die folgenden Fragen finden:

  1. Wie wird die Repository-Implementierungsklasse zur Laufzeit generiert und Methoden implementiert und injiziert?
  2. Verwendet Spring Data JPA CGlib oder Bytecode-Manipulationsbibliotheken, um die Methoden zu implementieren und dynamisch zu injizieren?

Könnten Sie bitte bei den oben genannten Fragen helfen und auch unterstützte Dokumentation bereitstellen?

Entwickler
quelle

Antworten:

144

Erstens findet keine Codegenerierung statt, was bedeutet: keine CGLib, überhaupt keine Bytecodegenerierung. Der grundlegende Ansatz besteht darin, dass eine JDK-Proxy-Instanz mithilfe der Spring- ProxyFactoryAPI programmgesteuert erstellt wird , um die Schnittstelle zu sichern, und MethodInterceptoralle Aufrufe der Instanz abfängt und die Methode an die entsprechenden Stellen weiterleitet:

  1. Wenn das Repository mit einem benutzerdefinierten Implementierungsteil initialisiert wurde ( Einzelheiten finden Sie in diesem Teil der Referenzdokumentation ) und die aufgerufene Methode in dieser Klasse implementiert ist, wird der Aufruf dort weitergeleitet.
  2. Wenn es sich bei der Methode um eine Abfragemethode handelt (siehe dies DefaultRepositoryInformation), wird der speicherspezifische Abfrageausführungsmechanismus aktiviert und führt die Abfrage aus, die beim Start für diese Methode ausgeführt werden soll. Zu diesem Zweck ist ein Auflösungsmechanismus vorhanden, der versucht, explizit deklarierte Abfragen an verschiedenen Stellen zu identifizieren (unter Verwendung @Queryvon JPA-benannten Abfragen für die Methode), die schließlich auf die Ableitungsableitung aus dem Methodennamen zurückgreifen. Informationen zur Erkennung des Abfragemechanismus finden Sie unter JpaQueryLookupStrategy. Die Parsing-Logik für die Abfrage-Ableitung finden Sie in PartTree. Die speicherspezifische Übersetzung in eine tatsächliche Abfrage ist zB in zu sehen JpaQueryCreator.
  3. Wenn keine der oben genannten Methoden zutrifft, muss die ausgeführte Methode von einer speicherspezifischen Repository-Basisklasse ( SimpleJpaRepositoryim Fall von JPA) implementiert werden, und der Aufruf wird an eine Instanz davon weitergeleitet.

Der Methoden-Interceptor, der diese Routing-Logik implementiert, ist QueryExecutorMethodInterceptordie High-Level-Routing-Logik, die hier zu finden ist .

Die Erstellung dieser Proxys ist in einer standardmäßigen Java-basierten Factory-Musterimplementierung gekapselt. Die übergeordnete Proxy-Erstellung finden Sie in RepositoryFactorySupport. Die speicherspezifischen Implementierungen fügen dann die erforderlichen Infrastrukturkomponenten hinzu, sodass Sie für JPA einfach Code wie folgt schreiben können:

EntityManager em =  // obtain an EntityManager
JpaRepositoryFactory factory = new JpaRepositoryFactory(em);
UserRepository repository = factory.getRepository(UserRepository.class);

Der Grund, warum ich dies ausdrücklich erwähne, ist, dass klar werden sollte, dass für nichts von diesem Code ein Spring-Container erforderlich ist, um überhaupt ausgeführt zu werden. Es benötigt Spring als Bibliothek auf dem Klassenpfad (weil wir es vorziehen, das Rad nicht neu zu erfinden), ist aber im Allgemeinen containerunabhängig.

Um die Integration in DI-Container zu vereinfachen, haben wir dann natürlich die Integration mit der Spring Java-Konfiguration erstellt, einem XML-Namespace, aber auch einer CDI-Erweiterung , damit Spring Data in einfachen CDI-Szenarien verwendet werden kann.

Oliver Drotbohm
quelle
3
Hallo Oliver, kannst du näher erläutern, wie der Frühling die @Repositorykommentierten Schnittstellen überhaupt entdeckt ? Ein Blick auf RepositoryFactorySupport#getRepository()zeigt, dass die Schnittstellenklasse als Parameter verwendet wird, sodass sie an einer anderen Stelle gefunden werden muss. Ich versuche insbesondere herauszufinden, wie eine mit Anmerkungen versehene Schnittstelle gefunden und automatisch eine JDK-Proxy-Bean generiert wird, die die Schnittstelle ähnlich wie Spring-Daten implementiert, jedoch für einen anwendungsspezifischen Zweck, der nicht mit Repositorys zusammenhängt.
Chris Rice
1
Vielleicht möchten Sie einen Blick darauf werfen RepositoryComponentProvider. Es passieren keine automatischen Ereignisse, sondern ein Komponentenscan für bestimmte Typen (entweder mit Anmerkungen versehen oder mit Anmerkungen versehen) und eine FactoryBeanfür jeden dieser Typen konfigurierte.
Oliver Drotbohm
2
Es tut mir leid, einen alten Thread zu kommentieren, aber ich war neugierig ... Sind die Repository-Proxys Singleton-Objekte? Wir sehen ein Problem, bei dem unser Code versucht, eine Repo-Methode aufzurufen, aber es scheint nie in der Lage zu sein, den Proxy aufzurufen. Es hängt einfach. Ich frage mich, ob es auf einen Singleton wartet, der beschäftigt ist.
iu.david