Spring Data JPA 工作原理 : 自定義JpaRespository介面卻不用提供實現
概述
當我們使用 Spring Data JPA 的時候,典型的用法是這樣的 :
- 將 spring-data-jpa 包,資料庫驅動包等新增為專案依賴;
- 配置檔案定義相應的資料來源;
- 為應用添加註解@EnableJpaRepositories;
- 定義業務領域實體類,比如通過@Entity註解;定義業務領域實體類,比如通過@Entity註解;
- 定義自己業務相關的的JPA repository介面,這些介面都繼承自JpaRepository或者JpaSpecificationExecutor;
- 將上面自定義JPA repository介面注入到服務層並使用它們進行相應的增刪改查;
在這個流程中,我們並沒有對自定義JPA repository介面提供任何實現,但神奇的是在服務層我們居然可以成功地注入該介面型別的bean並使用。那麼,問題來了,這個bean到底是個什麼東西呢,又是為什麼能夠成功注入呢 ?這篇文章我們基於Springboot應用模式和Spring的原始碼試圖回答一下這些問題 。
工作原理分析
自動配置 JpaRepositoriesAutoConfiguration
因為我們已經將spring-data-jpa jar
包,資料庫驅動jar
包都已經新增為了系統依賴,也就是說它們會出現在程式執行時的classpath
上,並且,我們已經配置了資料來源。現在在應用程式啟動過程中,springboot
JPA
自動配置的部分就開始工作了,具體的配置自動配置類是JpaRepositoriesAutoConfiguration
。
該自動配置有一點很重要,它匯入了 JpaRepositoriesAutoConfigureRegistrar
:
> java > @Import(JpaRepositoriesAutoConfigureRegistrar.class) >
而JpaRepositoriesAutoConfigureRegistrar
進而使用了註解@EnableJpaRepositories
。
@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration {
}
註解@EnableJpaRepositories指出了建立repository的FactoryBean
是什麼:
// 註解@EnableJpaRepositories部分原始碼
/**
* Returns the FactoryBean class to be used for each repository instance. Defaults to
* JpaRepositoryFactoryBean.
*
* @return
*/
Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;
這裡指出預設使用JpaRepositoryFactoryBean
為建立使用者自定義JpaRepository
的FactoryBean
。
JpaRepositoryFactoryBean
位於包org.springframework.data.jpa.repository.support
。
使用者自定義JpaRepository
作為bean註冊到容器
因為JpaRepositoriesAutoConfiguration
匯入了JpaRepositoriesAutoConfigureRegistrar
,而這是一個ImportBeanDefinitionRegistrar
,設計目的就是用於Springboot
自動配置機制中發現使用者自定義JpaRrepository
。
在應用程式啟動中ImportBeanDefinitionRegistrar
被處理階段,這個JpaRepositoriesAutoConfigureRegistrar
會被執行:
// 呼叫棧
//SpringApplication#run
//SpringApplication#refreshContext
//AbstractApplicationContext#refresh
//AbstractApplicationContext#invokeBeanFactoryPostProcessors
//PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
//ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
//ConfigurationClassPostProcessor#processConfigBeanDefinitions
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
/**
* 引數registrars會包含一項是JpaRepositoriesAutoConfigureRegistrar
**/
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
而JpaRepositoriesAutoConfigureRegistrar
又將任務交給了一個RepositoryConfigurationDelegate
來完成:
// 呼叫棧
// JpaRepositoriesAutoConfigureRegistrar#registerBeanDefinitions
// AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
new RepositoryConfigurationDelegate(getConfigurationSource(registry),
this.resourceLoader, this.environment).registerRepositoriesIn(registry,
getRepositoryConfigurationExtension());
}
// 呼叫棧
// 接上
// AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions
//RepositoryConfigurationDelegate#registerRepositoriesIn
/**
* Registers the found repositories in the given BeanDefinitionRegistry.
*
* @param registry
* @param extension
* @return BeanComponentDefinitions for all repository bean definitions found.
*/
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
RepositoryConfigurationExtension extension) {
if (LOG.isInfoEnabled()) {
// 如果你的日誌開啟的話,控制檯輸出上應該能看到這個資訊輸出
LOG.info("Bootstrapping Spring Data repositories in {} mode.", configurationSource.getBootstrapMode().name());
}
extension.registerBeansForRoot(registry, configurationSource);
// 為接下來將要通過包掃描發現的使用者自定義JpaRepository構造一個RepositoryBeanDefinitionBuilder,注意
// 這個 builder 已經知道了對應將要到來的使用者自定義JpaRepository bean註冊時,都使用JpaRepositoryFactoryBean
// 作為FactoryBean,這個資訊是由@EnableJpaRepositories定義的,通過configurationSource引數傳進來
RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension,
configurationSource, resourceLoader, environment);
List<BeanComponentDefinition> definitions = new ArrayList<>();
StopWatch watch = new StopWatch();
if (LOG.isDebugEnabled()) {
LOG.debug("Scanning for repositories in packages {}.",
configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
}
watch.start();
// extension.getRepositoryConfigurations() 會掃描相應的包並找到使用者自定義JpaRepository介面
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode);
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size());
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {
configurationsByRepositoryName.put(configuration.getRepositoryInterface(), configuration);
// 對於每個掃描找到的使用者自定義JpaRepository,構建一個BeanDefinitionBuilder,
// 就是在這個步驟中將該BeanDefinition同JpaRepositoryFactoryBean建立關係
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
extension.postProcess(definitionBuilder, configurationSource);
if (isXml) {
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}
// 這裡根據所發現的使用者自定義JpaRepository介面的名字構造一個bean名稱
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = configurationSource.generateBeanName(beanDefinition);
if (LOG.isTraceEnabled()) {
LOG.trace(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName, configuration.getRepositoryInterface(),
configuration.getRepositoryFactoryBeanClassName());
}
// 設定當前BeanDefinition的屬性factoryBeanObjectType為使用者自定義JpaRepository介面的全限定名
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 到目前為止針對所發現的一個使用者自定義JpaRepository介面,已經構造了一個這樣的bean定義:
// 1. bean 名稱根據使用者自定義JpaRepository介面的短名稱生成,比如:studentRepository
// 2. bean 的型別為使用者自定義JpaRepository介面
// 3. bean 實際使用時的實現類將由JpaRepositoryFactoryBean工廠生成 (beanClass)
// 現在把這個bean註冊到容器
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}
potentiallyLazifyRepositories(configurationsByRepositoryName, registry, configurationSource.getBootstrapMode());
watch.stop();
if (LOG.isInfoEnabled()) {
// 如果你的日誌開啟的話,控制檯輸出上應該能看到對上面過程的一個總結
LOG.info("Finished Spring Data repository scanning in {}ms. Found {} repository interfaces.", //
watch.getLastTaskTimeMillis(), configurations.size());
}
return definitions;
}
從上面的分析來看,不管使用者定義了多少個JpaRepository
,最終實際注入的bean都會來自JpaRepositoryFactoryBean
,而實際上,每個使用者自定義的JpaRepository
一般都是針對不同的領域實體。那麼這一點又是怎麼體現的呢?
我們繼續分析。
使用使用者自定義JpaRepository
bean
假如說服務層注入了一個使用者自定義的JpaRepository
,比如通過這種方式:
@Autowired
StudentRepository repo;
那麼在應用啟動過程中,這個bean會被例項化並被注入。上面我們已經分析過,針對每個使用者自定義的JpaRepository
(在這個例子中,也就是StudentRepository repo
)其實現類都來自JpaRepositoryFactoryBean
,現在我們看看這個bean的例項化過程。
首先每個使用者自定義JpaRepository
bean在首次被注入時,都會構造一個JpaRepositoryFactoryBean
物件:
// 這裡的引數就是使用者自定義的JpaRepository介面
public JpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
因為JpaRepositoryFactoryBean
同時也實現了InitializingBean
介面,所以在bean被建立後,它的方法afterPropertiesSet
會被呼叫:
public void afterPropertiesSet() {
// factory是用於建立使用者自定義JpaRepository bean的工廠
this.factory = createRepositoryFactory();
// ......
// 注意下面使用了 Lazy/Supplier 模式,程式碼看起來有些繞,
// 實際上要做的事情是:
// 1. 如果是 lazy 初始化模式,先不建立相應的 repository 例項,讓它儲存在一個 Lazy 物件中;
// 2. 如果不是 lazy 初始化模式,直接建立相應的 repository 例項,也儲存在 Lazy 物件中。
// 這裡 this.factory.getRepository 是真正建立 repository 例項的呼叫,
// 因為這裡的factory實際上是JpaRepository,我們下面會看它的getRepository方法實現
this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
if (!lazyInit) {
// 如果不是lazy初始化模式,這裡建立真正的的repository
this.repository.get();
}
}
createRepositoryFactory
該方法的第一件事情,就是呼叫createRepositoryFactory
,這個方法建立了一個JpaRepositoryFactory
物件,用於建立終端使用者自定義JpaRepository
對應的bean例項。
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
// 這裡建立了一個 JpaRepositoryFactory 物件
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
return jpaRepositoryFactory;
}
這裡需要提醒的是,JpaRepositoryFactory
類定義了生成最終JpaRepository
的bean使用實現類SimpleJpaRepository
:
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// SimpleJpaRepository 位於包 org.springframework.data.jpa.repository.support
return SimpleJpaRepository.class;
}
getRepository
JpaRepositoryFactory
繼承自RepositoryFactorySupport
,其方法getRepository
也在該基類中實現 :
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
if (LOG.isDebugEnabled()) {
LOG.debug("Initializing repository instance for {}…", repositoryInterface.getName());
}
Assert.notNull(repositoryInterface, "Repository interface must not be null!");
Assert.notNull(fragments, "RepositoryFragments must not be null!");
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
RepositoryInformation information = getRepositoryInformation(metadata, composition);
validate(information, composition);
// 下面語句最終建立一個 SimpleJpaRepository 物件,並且該物件是針對使用者自定義JpaRepository
// repositoryInterface 的,注意引數 information 根據 repositoryInterface 生成,其中
// 隱含了要操作哪個領域實體,或者說是操作哪個表。
// 為什麼會使用 SimpleJpaRepository ? 因為當前物件是一個JpaRepositoryFactory,
// 它指定了要使用 SimpleJpaRepository 作為 repositoryBaseClass
Object target = getTargetRepository(information);
// Create proxy
// SimpleJpaRepository 並不能滿足使用者自定義JpaRepository的所有方法,所以要為其建立
// 一個代理物件,這樣就可以增加更過的方法呼叫攔截器,對於那些自定義JpaRepository介面
// 中定義但在SimpleJpaRepository的方法,就會由這些攔截器來處理。
// 這裡的代理同時也考慮了對異常,和事務的處理
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
if (MethodInvocationValidator.supports(repositoryInterface)) {
result.addAdvice(new MethodInvocationValidator());
}
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
postProcessors.forEach(processor -> processor.postProcess(result, information));
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));
composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));
T repository = (T) result.getProxy(classLoader);
if (LOG.isDebugEnabled()) {
LOG.debug("Finished creation of repository instance for {}.", repositoryInterface.getName());
}
// 這裡返回的是SimpleJpaRepository物件的代理物件
return repository;
}
最後,當真正的JpaRepository
物件(也就是上面分析中所提到的SimpleJpaRepository
代理物件)被建立之後,包裹該物件的那個 JpaRepositoryFactoryBean
物件就是最終我們要使用的bean的FactoryBean
了,在Spring容器中,對應使用者自定義的JpaRepository
介面,儲存的實際上是一個JpaRepositoryFactoryBean
。
現在,因為首次注入導致的bean的例項化過程已經結束了,可以進行真正的注入了。根據上面的分析,每個使用者自定義JpaRepository
介面在容器中的bean實際上儲存的是一個JpaRepositoryFactoryBean
物件,這是一個FactoryBean
。對這樣一個bean進行注入時,會呼叫FactoryBean#getObject
揮著真正要注入的SimpleJpaRepository
代理物件。
總結
從上面的分析可以看到,雖然使用者沒有為每個自定義的JpaRepository
介面提供實現類,但是Spring Data JPA 最終將每個這樣的bean最終對映到了一個統一的實現類SimpleJpaRepository
的代理物件,而這個代理物件能支援所有每個自定義的JpaRepository
介面定義的功能和SimpleJpaRepository
類中定義的所有功能。
問題 : 那些在使用者自定義
JpaRepository
中定義但是並不屬於SimpleJpaRepository
的方法,是怎樣轉換成相應的SQL的?