最近在寫一個輪子但汞,準(zhǔn)備將 jpa 的功能移植到 hbase, 實現(xiàn) hbase 的對象查詢功能,于是乎互站,開始研究 jpa 的流程和原理私蕾,經(jīng)過一番折騰,終于大致明白了 jpa 是如何實現(xiàn)這種強大優(yōu)雅的查詢方式的胡桃。我把流程當(dāng)中重要的部分記錄下來踩叭,希望大佬多多指正。
項目依賴以及如何入門使用網(wǎng)上有大把的文章翠胰,介紹的很清晰容贝,這里就不說這部分的內(nèi)容了。
下面整理出我分析的一個思路之景,按步驟來斤富。
1 先定義一個簡單的查詢接口,作為一個調(diào)試跟蹤的例子, 代碼如下
@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
Account findByUserId(String userId);
Account findByPhone(String phone);
Account findByDeviceId(String deviceId);
}
2 service里面注入這個查詢接口
@Service
public class UserServiceImpl implements UserService {
@Resource
private AccountRepository accountRepository;
@Override
public void saveDeviceId(String deviceId) {
Account account = accountRepository.findByDeviceId(deviceId);
if (account != null) {
return;
}
}
}
3 啟動測試用例或者應(yīng)用
4 查看注入的查詢接口的是什么
斷點里面看的很清楚锻狗,注入的是一個動態(tài)代理生成的對象满力,實際類型是 SimpleJpaRepository.
查看這個類的類圖關(guān)系焕参,看看有什么
這個類里面代碼很多,就不貼出來了油额。方法涵蓋了查詢接口常用的數(shù)據(jù)庫操作叠纷,例如 find, delete, save。
我很好奇 springboot 是怎么構(gòu)造并且注入這個 SimpleJpaRepository 的......
5 搜索 這個類的引用關(guān)系, 如下圖
只有變量聲明潦嘶,返回類型涩嚣,并沒有 new 該對象,因為 spring 是使用反射創(chuàng)建對象的衬以。到這里還不是很清楚缓艳,繼續(xù)看看 JpaRepositoryFactory
7 查看 JpaRepositoryFactory 方法調(diào)用過程
沒有時序圖,這個方法調(diào)用關(guān)系圖看起來不是很清晰看峻,將就著看吧。最終調(diào)用了 RepositoryFactorySupport
的一個方法衙吩,從方法名看出來互妓,這里使用反射。方法里面也確實使用了反射實例化一個 SimpleJpaRepository 對象
到這里坤塞,你是不是還是有點懵逼冯勉,沒事,現(xiàn)在來總結(jié)剛剛分析的內(nèi)容
剛才一共出現(xiàn)了4個類的代碼
- JpaRepositoryFactoryBean
- RepositoryFactoryBeanSupport
- JpaRepositoryFactory
- RepositoryFactorySupport
他們之間的關(guān)系是有規(guī)律的
8 查看這4個類的關(guān)系
關(guān)系如下圖
看到這里灼狰,應(yīng)該明白了 jpa 是如何生成一個 SimpleJpaRepository 對象被你使用的。再不明白的話浮禾,你自己搜索這個類的引用交胚,一層一層找下去,覺得會執(zhí)行到的代碼打個斷點盈电,然后啟動應(yīng)用或者測試蝴簇,就會看到,上面貼出的代碼匆帚,基本上一個不漏的都會被執(zhí)行到熬词。
但是...... 到這里你是不是還不明白 SimpleJpaRepository 是如何注入到 spring 容器的,并且可以用 @Resource 來注入到應(yīng)用的各個地方吸重?
不明白是因為這個對象根本就沒有注入到容器中互拾,為什么?
剛剛有一個很重要的信息,就是 JpaRepositoryFactoryBean 是一種特殊的 bean, 實現(xiàn)了 FactoryBean 接口,
方法 getObject() 返回的是一個 SimpleJpaRepository 對象嚎幸。這說明了實際上是 JpaRepositoryFactoryBean 本身這個類的實例注入到 spring 容器當(dāng)中的颜矿。
現(xiàn)在的問題就是 JpaRepositoryFactoryBean 這個類如何實例化并注入到容器中
9 搜索 JpaRepositoryFactoryBean 類的引用
搜索后的調(diào)用層次是這樣的,從下往上,沒有列出方法的鞭铆,就是對類的引用或衡,其余是方法的調(diào)用
JpaRepositoryFactoryBean
JpaRepositoryConfigExtension.getRepositoryFactoryBeanClassName()
DefaultRepositoryConfiguration.getRepositoryFactoryBeanClassName()
RepositoryBeanDefinitionBuilder.build()
RepositoryConfigurationDelegate.registerRepositoriesIn()
AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions()
JpaRepositoriesAutoConfigureRegistrar
JpaRepositoriesAutoConfiguration
最后一個類的代碼
@Configuration
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class,JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
// springboot 啟動的時候會執(zhí)行 @Import 注解配置的類里面的 registerBeanDefinitions() 方法
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter(HibernateJpaAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration {
}
到這里應(yīng)該明白了吧焦影,這個類是 springboot 啟動時的一個自動配置的類,會執(zhí)行 jpa 的一些配置封断,初始化斯辰,實例化
JpaRepositoryFactoryBean 并且注入到容器中
再次搜索這個類的引用,發(fā)現(xiàn)可以找到 spring 配置文件坡疼,截取一部分彬呻,可以看到, springboot 啟動的時候會執(zhí)行各個組件的初始化柄瑰,包括 redis, mongodb......
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
為了驗證剛剛的引用關(guān)系闸氮,在 RepositoryBeanDefinitionBuilder.build() 方法里面打斷點,啟動應(yīng)用教沾,看會不會執(zhí)行
確實是進入了斷點蒲跨,執(zhí)行了剛剛分析的方法, 貼出這個方法的部分代碼
class RepositoryBeanDefinitionBuilder {
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// configuration.getRepositoryFactoryBeanClassName() 返回的 是 JpaRepositoryFactoryBean
//準(zhǔn)備對 JpaRepositoryFactoryBean 進行反射實例化
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName());
// configuration.getRepositoryInterface() 在這個例子返回的是 AccountRepository
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
configuration.getRepositoryBaseClassName()//
.ifPresent(it -> builder.addPropertyValue("repositoryBaseClass", it));
return builder;
}
}
看看調(diào)用這個方法的邏輯
public class RepositoryConfigurationDelegate {
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
RepositoryConfigurationExtension extension) {
// build 就是上面的方法
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
extension.postProcess(definitionBuilder, configurationSource);
// 這里已經(jīng)生成了一個可以注入容器的 BeanDefinition 對象
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = configurationSource.generateBeanName(beanDefinition);
beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// spring 容器中存儲的是 BeanDefinition 對象,該對象封裝了 對應(yīng)的 bean 的信息
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
你還可以在斷點里面查看 棧幀 的信息授翻,也驗證了剛剛貼出的引用關(guān)系或悲,
到目前為止,jpa 查詢接口是如何初始化的已經(jīng)分析出來了堪唐,這些代碼邏輯包含了各種設(shè)計模式巡语,模型抽象,對最新的java 版本的各種特性的使用, 例如8里面的 Function, Supplier, BiFunction, 方法引用等淮菠,很值得學(xué)習(xí)男公。