¿Cómo se implementan realmente los repositorios de Spring Data?

111

He estado trabajando con el repositorio Spring Data JPA en mi proyecto durante algún tiempo y conozco los siguientes puntos:

  • En las interfaces del repositorio, podemos agregar los métodos como findByCustomerNameAndPhone()(asumiendo que customerNamey phoneson campos en el objeto de dominio).
  • Luego, Spring proporciona la implementación implementando los métodos de interfaz del repositorio anteriores en tiempo de ejecución (durante la ejecución de la aplicación).

Estoy interesado en cómo se ha codificado esto y he mirado el código fuente y las API de Spring JPA, pero no pude encontrar respuestas a las preguntas a continuación:

  1. ¿Cómo se genera la clase de implementación del repositorio en tiempo de ejecución y se implementan e inyectan los métodos?
  2. ¿Spring Data JPA usa CGlib o cualquier biblioteca de manipulación de código de bytes para implementar los métodos e inyectar dinámicamente?

¿Podría ayudarme con las consultas anteriores y también proporcionar documentación compatible?

desarrollador
fuente

Respuestas:

144

En primer lugar, no hay generación de código, lo que significa: no hay CGLib, no hay generación de código de bytes en absoluto. El enfoque fundamental es que una instancia de proxy JDK se crea mediante programación utilizando la ProxyFactoryAPI de Spring para respaldar la interfaz e MethodInterceptorintercepta todas las llamadas a la instancia y enruta el método a los lugares apropiados:

  1. Si el repositorio se ha inicializado con una parte de implementación personalizada (consulte esa parte de la documentación de referencia para obtener más detalles) y el método invocado se implementa en esa clase, la llamada se enruta allí.
  2. Si el método es un método de consulta (vea DefaultRepositoryInformationcómo se determina), el mecanismo de ejecución de consultas específico de la tienda se activa y ejecuta la consulta que se determina que se ejecutará para ese método al inicio. Para eso, existe un mecanismo de resolución que intenta identificar consultas declaradas explícitamente en varios lugares (utilizando @Queryen el método, consultas con nombre JPA) eventualmente recurriendo a la derivación de consultas a partir del nombre del método. Para la detección del mecanismo de consulta, consulte JpaQueryLookupStrategy. La lógica de análisis para la derivación de consultas se puede encontrar en PartTree. La traducción específica de la tienda en una consulta real se puede ver, por ejemplo, en JpaQueryCreator.
  3. Si nada de lo anterior se aplica, el método ejecutado debe ser uno implementado por una clase base de repositorio específica de la tienda ( SimpleJpaRepositoryen el caso de JPA) y la llamada se enruta a una instancia de eso.

El interceptor de métodos que implementa esa lógica de enrutamiento es QueryExecutorMethodInterceptor, la lógica de enrutamiento de alto nivel se puede encontrar aquí .

La creación de esos proxies está encapsulada en una implementación estándar de patrón de fábrica basada en Java. La creación de proxy de alto nivel se puede encontrar en RepositoryFactorySupport. Las implementaciones específicas de la tienda luego agregan los componentes de infraestructura necesarios para que para JPA pueda seguir adelante y simplemente escribir código como este:

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

La razón por la que lo menciono explícitamente es que debería quedar claro que, en su esencia, nada de ese código requiere que se ejecute un contenedor Spring en primer lugar. Necesita Spring como una biblioteca en la ruta de clases (porque preferimos no reinventar la rueda), pero es independiente de los contenedores en general.

Para facilitar la integración con los contenedores DI, por supuesto, hemos construido la integración con la configuración de Spring Java, un espacio de nombres XML, pero también una extensión CDI , de modo que Spring Data se pueda usar en escenarios CDI simples.

Oliver Drotbohm
fuente
3
Hola Oliver, ¿puedes explicar cómo Spring descubre las @Repositoryinterfaces anotadas en primer lugar? Al RepositoryFactorySupport#getRepository()observar, se muestra que toma la clase de interfaz como parámetro, por lo que debe descubrirse en otro lugar. En particular, estoy tratando de averiguar cómo encontrar una interfaz anotada y generar automáticamente un bean proxy JDK que implemente la interfaz, muy parecido a Spring-Data, pero para un propósito específico de la aplicación no relacionado con los repositorios.
Chris Rice
1
Es posible que desee echar un vistazo RepositoryComponentProvider. No suceden cosas automáticas, sino un escaneo de componentes para ciertos tipos (ya sea con anotaciones o con una anotación) y una FactoryBeanconfiguración para cada uno de ellos.
Oliver Drotbohm
2
Perdón por comentar sobre un hilo antiguo, pero tenía curiosidad ... ¿Son los proxies del repositorio objetos singleton? Estamos viendo un problema por el cual nuestro código intenta llamar a un método de repositorio, pero parece que nunca puede realizar la llamada en el proxy. Simplemente cuelga. Me pregunto si está esperando un singleton ocupado.
iu.david