1. 程式人生 > >Spring Data JPA 工作原理 : 自定義JpaRespository介面卻不用提供實現

Spring Data JPA 工作原理 : 自定義JpaRespository介面卻不用提供實現

概述

當我們使用 Spring Data JPA 的時候,典型的用法是這樣的 :

  1. 將 spring-data-jpa 包,資料庫驅動包等新增為專案依賴;
  2. 配置檔案定義相應的資料來源;
  3. 為應用添加註解@EnableJpaRepositories;
  4. 定義業務領域實體類,比如通過@Entity註解;定義業務領域實體類,比如通過@Entity註解;
  5. 定義自己業務相關的的JPA repository介面,這些介面都繼承自JpaRepository或者JpaSpecificationExecutor;
  6. 將上面自定義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為建立使用者自定義JpaRepositoryFactoryBean

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的?