Comment les référentiels Spring Data sont-ils réellement mis en œuvre?

111

Je travaille avec le référentiel Spring Data JPA dans mon projet depuis un certain temps et je connais les points ci-dessous:

  • Dans les interfaces du référentiel, nous pouvons ajouter les méthodes comme findByCustomerNameAndPhone()(en supposant que customerNameet phonesont des champs dans l'objet de domaine).
  • Ensuite, Spring fournit l'implémentation en implémentant les méthodes d'interface de référentiel ci-dessus au moment de l'exécution (pendant l'exécution de l'application).

Je suis intéressé par la façon dont cela a été codé et j'ai regardé le code source et les API Spring JPA, mais je n'ai pas pu trouver de réponses aux questions ci-dessous:

  1. Comment la classe d'implémentation du référentiel est-elle générée au moment de l'exécution et les méthodes sont-elles implémentées et injectées?
  2. Spring Data JPA utilise-t-il CGlib ou des bibliothèques de manipulation de bytecode pour implémenter les méthodes et injecter dynamiquement?

Pourriez-vous s'il vous plaît aider avec les requêtes ci-dessus et également fournir toute documentation prise en charge?

développeur
la source

Réponses:

144

Tout d'abord, il n'y a pas de génération de code en cours, ce qui signifie: pas de CGLib, pas de génération de code d'octet du tout. L'approche fondamentale est qu'une instance de proxy JDK est créée par programme à l'aide de l' ProxyFactoryAPI de Spring pour sauvegarder l'interface et MethodInterceptorintercepte tous les appels à l'instance et achemine la méthode aux endroits appropriés:

  1. Si le référentiel a été initialisé avec une partie d'implémentation personnalisée (voir cette partie de la documentation de référence pour plus de détails) et que la méthode appelée est implémentée dans cette classe, l'appel y est acheminé.
  2. Si la méthode est une méthode de requête (voir DefaultRepositoryInformationcomment cela est déterminé), le mécanisme d'exécution de requête spécifique au magasin entre en action et exécute la requête déterminée à être exécutée pour cette méthode au démarrage. Pour cela, un mécanisme de résolution est en place qui tente d'identifier les requêtes explicitement déclarées à divers endroits (en utilisant @Querysur la méthode, les requêtes nommées JPA) en retombant finalement vers la dérivation de requête à partir du nom de la méthode. Pour la détection du mécanisme de requête, voir JpaQueryLookupStrategy. La logique d'analyse pour la dérivation de la requête se trouve dans PartTree. La traduction spécifique au magasin en une requête réelle peut être vue par exemple dans JpaQueryCreator.
  3. Si aucune des solutions ci-dessus ne s'applique, la méthode exécutée doit être implémentée par une classe de base de référentiel spécifique au magasin ( SimpleJpaRepositorydans le cas de JPA) et l'appel est acheminé vers une instance de celle-ci.

L'intercepteur de méthode implémentant cette logique de routage est QueryExecutorMethodInterceptor, la logique de routage de haut niveau peut être trouvée ici .

La création de ces proxies est encapsulée dans une implémentation de modèle Factory standard basée sur Java. La création de proxy de haut niveau se trouve dans RepositoryFactorySupport. Les implémentations spécifiques au magasin ajoutent ensuite les composants d'infrastructure nécessaires afin que pour JPA, vous puissiez continuer et écrire simplement du code comme celui-ci:

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

La raison pour laquelle je mentionne cela explicitement est qu'il devrait devenir clair que, dans son cœur, rien de ce code ne nécessite un conteneur Spring pour s'exécuter en premier lieu. Il a besoin de Spring comme bibliothèque sur le chemin de classe (car nous préférons ne pas réinventer la roue), mais est indépendant du conteneur en général.

Pour faciliter l'intégration avec les conteneurs DI, nous avons bien sûr ensuite construit une intégration avec la configuration Spring Java, un espace de noms XML, mais aussi une extension CDI , afin que Spring Data puisse être utilisé dans des scénarios CDI simples.

Oliver Drotbohm
la source
3
Salut Oliver, pouvez-vous expliquer comment Spring découvre les @Repositoryinterfaces annotées en premier lieu? En regardant RepositoryFactorySupport#getRepository()montrer qu'elle prend la classe d'interface comme paramètre, elle doit donc être découverte ailleurs. J'essaie en particulier de comprendre comment trouver une interface annotée et générer automatiquement un bean proxy JDK qui implémente l'interface, un peu comme spring-data, mais dans un but spécifique à l'application non lié aux référentiels.
Chris Rice
1
Vous voudrez peut-être jeter un coup d'œil RepositoryComponentProvider. Il n'y a pas de choses automatiques qui se passent mais une analyse des composants pour certains types (annotés ou porteurs d'une annotation) et une FactoryBeanconfiguration pour chacun d'entre eux.
Oliver Drotbohm
2
Désolé de commenter un ancien fil de discussion, mais c'était curieux ... Les proxies du référentiel sont-ils des objets singleton? Nous voyons un problème où notre code essaie d'appeler une méthode de dépôt, mais il ne semble jamais être en mesure de faire l'appel sur le proxy. Il accroche juste. Je me demande si ça attend un singleton occupé.
iu.david