springboot JPA 啟動流程與原理分析

最近在寫一個輪子但汞,準(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 查看注入的查詢接口的是什么

圖1 AccountRepository的實際類型.png

斷點里面看的很清楚锻狗,注入的是一個動態(tài)代理生成的對象满力,實際類型是 SimpleJpaRepository.
查看這個類的類圖關(guān)系焕参,看看有什么


SimpleJpaRepository類圖關(guān)系.png

這個類里面代碼很多,就不貼出來了油额。方法涵蓋了查詢接口常用的數(shù)據(jù)庫操作叠纷,例如 find, delete, save。
我很好奇 springboot 是怎么構(gòu)造并且注入這個 SimpleJpaRepository 的......

5 搜索 這個類的引用關(guān)系, 如下圖

上面的方法調(diào)用下面的.png

只有變量聲明潦嘶,返回類型涩嚣,并沒有 new 該對象,因為 spring 是使用反射創(chuàng)建對象的衬以。到這里還不是很清楚缓艳,繼續(xù)看看 JpaRepositoryFactory

7 查看 JpaRepositoryFactory 方法調(diào)用過程

方法調(diào)用關(guān)系.png

沒有時序圖,這個方法調(diào)用關(guān)系圖看起來不是很清晰看峻,將就著看吧。最終調(diào)用了 RepositoryFactorySupport
的一個方法衙吩,從方法名看出來互妓,這里使用反射。方法里面也確實使用了反射實例化一個 SimpleJpaRepository 對象

代碼圖3 藍色字體的內(nèi)容剛好驗證了 圖1 的信息.png

到這里坤塞,你是不是還是有點懵逼冯勉,沒事,現(xiàn)在來總結(jié)剛剛分析的內(nèi)容
剛才一共出現(xiàn)了4個類的代碼

  • JpaRepositoryFactoryBean
  • RepositoryFactoryBeanSupport
  • JpaRepositoryFactory
  • RepositoryFactorySupport

他們之間的關(guān)系是有規(guī)律的

8 查看這4個類的關(guān)系

關(guān)系如下圖

把這個類圖和圖3摹芙,圖4的代碼對應(yīng)起來.png
代碼圖4.png

看到這里灼狰,應(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í)行


斷點調(diào)試查看.png

確實是進入了斷點蒲跨,執(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)系或悲,


JVM里面的方法調(diào)用棧幀.png

到目前為止,jpa 查詢接口是如何初始化的已經(jīng)分析出來了堪唐,這些代碼邏輯包含了各種設(shè)計模式巡语,模型抽象,對最新的java 版本的各種特性的使用, 例如8里面的 Function, Supplier, BiFunction, 方法引用等淮菠,很值得學(xué)習(xí)男公。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市合陵,隨后出現(xiàn)的幾起案子枢赔,更是在濱河造成了極大的恐慌,老刑警劉巖曙寡,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糠爬,死亡現(xiàn)場離奇詭異,居然都是意外死亡举庶,警方通過查閱死者的電腦和手機执隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來户侥,“玉大人镀琉,你說我怎么就攤上這事∪锾疲” “怎么了屋摔?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長替梨。 經(jīng)常有香客問我钓试,道長装黑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任弓熏,我火速辦了婚禮恋谭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挽鞠。我一直安慰自己疚颊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布信认。 她就那樣靜靜地躺著材义,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嫁赏。 梳的紋絲不亂的頭發(fā)上其掂,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天,我揣著相機與錄音潦蝇,去河邊找鬼清寇。 笑死,一個胖子當(dāng)著我的面吹牛护蝶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翩迈,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼持灰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了负饲?” 一聲冷哼從身側(cè)響起堤魁,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎返十,沒想到半個月后妥泉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡洞坑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年盲链,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迟杂。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡刽沾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出排拷,到底是詐尸還是另有隱情侧漓,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布监氢,位于F島的核電站布蔗,受9級特大地震影響藤违,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纵揍,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一顿乒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骡男,春花似錦淆游、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吮炕,卻和暖如春腊脱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背龙亲。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工陕凹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳄炉。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓杜耙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拂盯。 傳聞我的和親對象是個殘疾皇子佑女,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,587評論 2 350

推薦閱讀更多精彩內(nèi)容