Como os repositórios Spring Data são realmente implementados?

111

Trabalho com o repositório Spring Data JPA em meu projeto há algum tempo e conheço os pontos abaixo:

  • Nas interfaces do repositório, podemos adicionar os métodos como findByCustomerNameAndPhone()(assumindo customerNamee phonesão campos no objeto de domínio).
  • Em seguida, o Spring fornece a implementação implementando os métodos de interface do repositório acima no tempo de execução (durante a execução do aplicativo).

Estou interessado em como isso foi codificado e olhei o código-fonte e as APIs do Spring JPA, mas não consegui encontrar as respostas para as perguntas abaixo:

  1. Como a classe de implementação do repositório é gerada em tempo de execução e métodos sendo implementados e injetados?
  2. O Spring Data JPA usa CGlib ou qualquer biblioteca de manipulação de bytecode para implementar os métodos e injetar dinamicamente?

Você poderia ajudar com as questões acima e também fornecer qualquer documentação compatível?

desenvolvedor
fonte

Respostas:

144

Em primeiro lugar, não há geração de código em andamento, o que significa: sem CGLib, nenhuma geração de código de byte. A abordagem fundamental é que uma instância do proxy JDK é criada programaticamente usando a ProxyFactoryAPI do Spring para apoiar a interface e MethodInterceptorintercepta todas as chamadas para a instância e encaminha o método para os locais apropriados:

  1. Se o repositório foi inicializado com uma parte de implementação customizada (consulte essa parte da documentação de referência para obter detalhes), e o método chamado foi implementado nessa classe, a chamada é roteada para lá.
  2. Se o método for um método de consulta (veja DefaultRepositoryInformationcomo isso é determinado), o mecanismo de execução de consulta específico da loja entra em ação e executa a consulta determinada a ser executada para aquele método na inicialização. Para isso, existe um mecanismo de resolução que tenta identificar consultas declaradas explicitamente em vários lugares (usando @Queryno método, consultas nomeadas JPA), eventualmente voltando para a derivação da consulta a partir do nome do método. Para a detecção do mecanismo de consulta, consulteJpaQueryLookupStrategy . A lógica de análise para a derivação da consulta pode ser encontrada em PartTree. A tradução específica da loja em uma consulta real pode ser vista, por exemplo, em JpaQueryCreator.
  3. Se nenhuma das opções acima se aplicar, o método executado deve ser implementado por uma classe base de repositório específica da loja (SimpleJpaRepository no caso de JPA) e a chamada é roteada para uma instância desse.

O método interceptor que implementa essa lógica de roteamento é QueryExecutorMethodInterceptor , a lógica de roteamento de alto nível pode ser encontrada aqui .

A criação desses proxies é encapsulada em uma implementação padrão de fábrica baseada em Java. A criação de proxy de alto nível pode ser encontrada emRepositoryFactorySupport . Em seguida, as implementações específicas da loja adicionam os componentes de infraestrutura necessários para que, para JPA, você possa seguir em frente e apenas escrever o código como este:

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

O motivo pelo qual menciono isso explicitamente é que deve ficar claro que, em seu núcleo, nada desse código exige que um contêiner Spring seja executado em primeiro lugar. Ele precisa do Spring como uma biblioteca no caminho de classe (porque preferimos não reinventar a roda), mas é agnóstico de contêiner em geral.

Para facilitar a integração com os contêineres DI, é claro que construímos a integração com a configuração Spring Java, um namespace XML, mas também uma extensão CDI , para que o Spring Data possa ser usado em cenários CDI simples.

Oliver Drotbohm
fonte
3
Olá Oliver, você pode explicar como o Spring descobriu as @Repositoryinterfaces anotadas em primeiro lugar? Olhando para RepositoryFactorySupport#getRepository()mostrar que leva a classe de interface como parâmetro, deve ser descoberta em outro lugar. Estou particularmente tentando descobrir como encontrar uma interface anotada e gerar automaticamente um bean de proxy JDK que implementa a interface, muito parecido com spring-data, mas para um propósito específico de aplicativo não relacionado a Repositórios.
Chris Rice
1
Você pode querer dar uma olhada RepositoryComponentProvider. Não há coisas automáticas acontecendo, mas uma varredura de componente para certos tipos (seja anotado ou carregando uma anotação) e um FactoryBeanconfigurado para cada um deles.
Oliver Drotbohm
2
Desculpe por comentar sobre um tópico antigo, mas estava curioso ... Os proxies do repositório são objetos singleton? Estamos vendo um problema em que nosso código está tentando chamar um método repo, mas nunca parece ser capaz de fazer a chamada no proxy. Ele simplesmente trava. Eu me pergunto se ele está esperando por um solteirão que está ocupado.
iu.david