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 Methoden wie hinzufügen
findByCustomerNameAndPhone()
(vorausgesetzt customerName
und phone
sind Felder im Domänenobjekt).
- Anschließend stellt Spring die Implementierung bereit, indem es die obigen Repository-Schnittstellenmethoden zur Laufzeit (während der Ausführung der Anwendung) implementiert.
Ich interessiere mich dafür, wie dies codiert wurde, und habe mir den Quellcode und die APIs von Spring JPA angesehen, aber ich konnte keine Antworten auf die folgenden Fragen finden:
- Wie wird die Repository-Implementierungsklasse zur Laufzeit generiert und Methoden implementiert und injiziert?
- Verwendet Spring Data JPA CGlib oder irgendwelche Bytecode-Manipulationsbibliotheken, um die Methoden zu implementieren und dynamisch einzufügen?
Könnten Sie bitte bei den obigen Fragen helfen und auch unterstützte Dokumentation bereitstellen?
Zunächst einmal findet keine Code-Generierung statt, was bedeutet: überhaupt keine CGLib, keine Byte-Code-Generierung. Der grundlegende Ansatz besteht darin, dass eine JDK-Proxy-Instanz programmgesteuert mit Spring erstellt wird ProxyFactory
API zur Unterstützung der Schnittstelle und a MethodInterceptor
fängt alle Aufrufe an die Instanz ab und leitet die Methode an die entsprechenden Stellen weiter:
- Wenn das Repository mit einem benutzerdefinierten Implementierungsteil initialisiert wurde (siehe dieser Teil der Referenzdokumentation für Details) und die aufgerufene Methode in dieser Klasse implementiert ist, wird der Aufruf dorthin geleitet.
- Wenn die Methode eine Abfragemethode ist (vgl
DefaultRepositoryInformation
wie das bestimmt wird), springt der geschäftsspezifische Abfrageausführungsmechanismus ein und führt die Abfrage aus, die bestimmt wurde, um für dieses Verfahren beim Start ausgeführt zu werden. Dafür gibt es einen Auflösungsmechanismus, der versucht, explizit deklarierte Abfragen an verschiedenen Stellen zu identifizieren (mittels @Query
auf der Methode, JPA-benannte Abfragen) schließlich auf die Abfrageableitung aus dem Methodennamen zurückgreifen. Zur Erkennung des Abfragemechanismus siehe JpaQueryLookupStrategy
. Die Parsing-Logik für die Abfrageableitung finden Sie in PartTree
. Die filialspezifische Übersetzung in eine tatsächliche Abfrage ist z. B. in zu sehen JpaQueryCreator
.
- Wenn keiner der oben genannten Punkte zutrifft, muss die ausgeführte Methode von einer geschäftsspezifischen Repository-Basisklasse implementiert werden (
SimpleJpaRepository
im Fall von JPA) und der Anruf wird in eine Instanz davon geleitet.
Der Methoden-Interceptor, der diese Routing-Logik implementiert, ist QueryExecutorMethodInterceptor
kann die High-Level-Routing-Logik gefunden werden Hier.
Die Erstellung dieser Proxys ist in eine standardmäßige Java-basierte Factory-Pattern-Implementierung eingekapselt. Die High-Level-Proxy-Erstellung finden Sie in RepositoryFactorySupport
. Die geschäftsspezifischen Implementierungen fügen dann die erforderlichen Infrastrukturkomponenten hinzu, sodass Sie für JPA fortfahren und einfach Code wie diesen schreiben können:
EntityManager em = … // obtain an EntityManager
JpaRepositoryFactory factory = new JpaRepositoryFactory(em);
UserRepository repository = factory.getRepository(UserRepository.class);
Der Grund, warum ich das ausdrücklich erwähne, ist, dass klar werden sollte, dass nichts von diesem Code im Kern erfordert, dass ein Spring-Container überhaupt ausgeführt wird. Es benötigt Spring als Bibliothek auf dem Klassenpfad (weil wir es vorziehen, das Rad nicht neu zu erfinden), ist aber im Allgemeinen Container-agnostisch.
Um die Integration mit DI-Containern zu erleichtern, haben wir dann natürlich eine Integration mit Spring Java-Konfiguration, einem XML-Namespace, aber auch einer CDI-Erweiterungsodass Spring Data in einfachen CDI-Szenarien verwendet werden kann.