0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring Boot啟動優(yōu)化實踐

jf_ro2CN3Fa ? 來源:稀土掘金 ? 2023-02-23 10:26 ? 次閱讀

背景

耗時問題排查

觀察 SpringBoot 啟動 run 方法

監(jiān)控 Bean 注入耗時

優(yōu)化方案

如何解決掃描路徑過多?

如何解決 Bean 初始化高耗時?

新的問題

SpringBoot 自動化裝配,讓人防不勝防

使用 starter 機(jī)制,開箱即用

背景

公司 SpringBoot 項目在日常開發(fā)過程中發(fā)現(xiàn)服務(wù)啟動過程異常緩慢,常常需要6-7分鐘才能暴露端口,嚴(yán)重降低開發(fā)效率。通過 SpringBoot 的 SpringApplicationRunListener 、BeanPostProcessor 原理和源碼調(diào)試等手段排查發(fā)現(xiàn),在 Bean 掃描和 Bean 注入這個兩個階段有很大的性能瓶頸。

通過 JavaConfig 注冊 Bean, 減少 SpringBoot 的掃描路徑,同時基于 Springboot 自動配置原理對第三方依賴優(yōu)化改造,將服務(wù)本地啟動時間從7min 降至40s 左右的過程。 本文會涉及以下知識點(diǎn):

基于 SpringApplicationRunListener 原理觀察 SpringBoot 啟動 run 方法;

基于 BeanPostProcessor 原理監(jiān)控 Bean 注入耗時;

SpringBoot Cache 自動化配置原理;

SpringBoot 自動化配置原理及 starter 改造;

耗時問題排查

SpringBoot 服務(wù)啟動耗時排查,目前有2個思路:

排查 SpringBoot 服務(wù)的啟動過程;

排查 Bean 的初始化耗時;

觀察 SpringBoot 啟動 run 方法

該項目使用基于 SpringBoot 改造的內(nèi)部微服務(wù)組件 XxBoot 作為服務(wù)端實現(xiàn),其啟動流程與 SpringBoot 類似,分為 ApplicationContext 構(gòu)造和 ApplicationContext 啟動兩部分,即通過構(gòu)造函數(shù)實例化 ApplicationContext 對象,并調(diào)用其 run 方法啟動服務(wù):

publicclassApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(Application.class,args);
}
}

publicstaticConfigurableApplicationContextrun(Class[]primarySources,String[]args){
returnnewSpringApplication(primarySources).run(args);
}

ApplicationContext 對象構(gòu)造過程,主要做了自定義 Banner 設(shè)置、應(yīng)用類型推斷、配置源設(shè)置等工作,不做特殊擴(kuò)展的話,大部分項目都是差不多的,不太可能引起耗時問題。通過在 run 方法中打斷點(diǎn),啟動后很快就運(yùn)行到斷點(diǎn)位置,也能驗證這一點(diǎn)。接下就是重點(diǎn)排查 run 方法的啟動過程中有哪些性能瓶頸?SpringBoot 的啟動過程非常復(fù)雜,慶幸的是 SpringBoot 本身提供的一些機(jī)制,將 SpringBoot 的啟動過程劃分了多個階段,這個階段劃分的過程就體現(xiàn)在 SpringApplicationRunListener 接口中,該接口將 ApplicationContext 對象的 run 方法劃分成不同的階段:

publicinterfaceSpringApplicationRunListener{
//run方法第一次被執(zhí)行時調(diào)用,早期初始化工作
voidstarting();
//environment創(chuàng)建后,ApplicationContext創(chuàng)建前
voidenvironmentPrepared(ConfigurableEnvironmentenvironment);
//ApplicationContext實例創(chuàng)建,部分屬性設(shè)置了
voidcontextPrepared(ConfigurableApplicationContextcontext);
//ApplicationContext加載后,refresh前
voidcontextLoaded(ConfigurableApplicationContextcontext);
//refresh后
voidstarted(ConfigurableApplicationContextcontext);
//所有初始化完成后,run結(jié)束前
voidrunning(ConfigurableApplicationContextcontext);
//初始化失敗后
voidfailed(ConfigurableApplicationContextcontext,Throwableexception);
}

目前,SpringBoot 中自帶的 SpringApplicationRunListener 接口只有一個實現(xiàn)類:EventPublishingRunListener,該實現(xiàn)類作用:通過觀察者模式的事件機(jī)制,在 run 方法的不同階段觸發(fā) Event 事件,ApplicationListener 的實現(xiàn)類們通過監(jiān)聽不同的 Event 事件對象觸發(fā)不同的業(yè)務(wù)處理邏輯。

?

通過自定義實現(xiàn) ApplicationListener 實現(xiàn)類,可以在 SpringBoot 啟動的不同階段,實現(xiàn)一定的處理,可見SpringApplicationRunListener 接口給 SpringBoot 帶來了擴(kuò)展性。

?

這里我們不必深究實現(xiàn)類 EventPublishingRunListener 的功能,但是可以通過 SpringApplicationRunListener 原理,「添加一個自定義的實現(xiàn)類,在不同階段結(jié)束時打印下當(dāng)前時間,通過計算不同階段的運(yùn)行時間,就能大體定位哪些階段耗時比較高」 ,然后重點(diǎn)排查這些階段的代碼。先看下 SpringApplicationRunListener 的實現(xiàn)原理,其劃分不同階段的邏輯體現(xiàn)在 ApplicationContext 的 run 方法中:

publicConfigurableApplicationContextrun(String...args){
...
//加載所有SpringApplicationRunListener的實現(xiàn)類
SpringApplicationRunListenerslisteners=getRunListeners(args);
//調(diào)用了starting
listeners.starting();
try{
ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);
//調(diào)用了environmentPrepared
ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
BannerprintedBanner=printBanner(environment);
context=createApplicationContext();
exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context);
//內(nèi)部調(diào)用了contextPrepared、contextLoaded
prepareContext(context,environment,listeners,applicationArguments,printedBanner);
refreshContext(context);
afterRefresh(context,applicationArguments);
stopWatch.stop();
if(this.logStartupInfo){
newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);
}
//調(diào)用了started
listeners.started(context);
callRunners(context,applicationArguments);
}
catch(Throwableex){
//內(nèi)部調(diào)用了failed
handleRunFailure(context,ex,exceptionReporters,listeners);
thrownewIllegalStateException(ex);
}
try{
//調(diào)用了running
listeners.running(context);
}
catch(Throwableex){
handleRunFailure(context,ex,exceptionReporters,null);
thrownewIllegalStateException(ex);
}
returncontext;
}

run 方法中 getRunListeners(args) 通過 SpringFactoriesLoader 加載 classpath 下 META-INF/spring.factotries 中配置的所有 SpringApplicationRunListener 的實現(xiàn)類,通過反射實例化后,存到局部變量 listeners 中,其類型為 SpringApplicationRunListeners;然后在 run 方法不同階段通過調(diào)用 listeners 的不同階段方法來觸發(fā) SpringApplicationRunListener 所有實現(xiàn)類的階段方法調(diào)用。

因此,只要編寫一個 SpringApplicationRunListener 的自定義實現(xiàn)類,在實現(xiàn)接口不同階段方法時,打印當(dāng)前時間;并在 META-INF/spring.factotries 中配置該類后,該類也會實例化,存到 listeners 中;在不同階段結(jié)束時打印結(jié)束時間,以此來評估不同階段的執(zhí)行耗時。在項目中添加實現(xiàn)類 MySpringApplicationRunListener :

@Slf4j
publicclassMySpringApplicationRunListenerimplementsSpringApplicationRunListener{
//這個構(gòu)造函數(shù)不能少,否則反射生成實例會報錯
publicMySpringApplicationRunListener(SpringApplicationsa,String[]args){
}
@Override
publicvoidstarting(){
log.info("starting{}",LocalDateTime.now());
}
@Override
publicvoidenvironmentPrepared(ConfigurableEnvironmentenvironment){
log.info("environmentPrepared{}",LocalDateTime.now());
}
@Override
publicvoidcontextPrepared(ConfigurableApplicationContextcontext){
log.info("contextPrepared{}",LocalDateTime.now());
}
@Override
publicvoidcontextLoaded(ConfigurableApplicationContextcontext){
log.info("contextLoaded{}",LocalDateTime.now());
}
@Override
publicvoidstarted(ConfigurableApplicationContextcontext){
log.info("started{}",LocalDateTime.now());
}
@Override
publicvoidrunning(ConfigurableApplicationContextcontext){
log.info("running{}",LocalDateTime.now());
}
@Override
publicvoidfailed(ConfigurableApplicationContextcontext,Throwableexception){
log.info("failed{}",LocalDateTime.now());
}
}

「這邊 (SpringApplication sa, String[] args) 參數(shù)類型的構(gòu)造函數(shù)不能少」 ,因為源碼中限定了使用該參數(shù)類型的構(gòu)造函數(shù)反射生成實例。

在 resources 文件下的 META-INF/spring.factotries 文件中配置上該類:

>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element實現(xiàn)的后臺管理系統(tǒng)+用戶小程序,支持RBAC動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
>
>*項目地址:
>*視頻教程

#RunListeners
org.springframework.boot.SpringApplicationRunListener=
com.xxx.ad.diagnostic.tools.api.MySpringApplicationRunListener

?

run 方法中是通過 getSpringFactoriesInstances 方法來獲取 META-INF/spring.factotries 下配置的 SpringApplicationRunListener 的實現(xiàn)類,其底層是依賴 SpringFactoriesLoader 來獲取配置的類的全限定類名,然后反射生成實例;這種方式在 SpringBoot 用的非常多,如 EnableAutoConfiguration、ApplicationListener、ApplicationContextInitializer 等。

?

重啟服務(wù),觀察 MySpringApplicationRunListener 的日志輸出,發(fā)現(xiàn)主要耗時都在 contextLoaded 和 started 兩個階段之間,在這兩個階段之間調(diào)用了2個方法:refreshContext 和 afterRefresh 方法,而 refreshContext 底層調(diào)用的是 AbstractApplicationContext#refresh,Spring 初始化 context 的核心方法之一就是這個 refresh。

cd7651c2-b2c9-11ed-bfe3-dac502259ad0.jpg

Spring 初始化 context

至此基本可以斷定,高耗時的原因就是在初始化 Spring 的 context,然而這個方法依然十分復(fù)雜,好在 refresh 方法也將初始化 Spring 的 context 的過程做了整理,并詳細(xì)注釋了各個步驟的作用:

cd87e572-b2c9-11ed-bfe3-dac502259ad0.jpg

初始化 Spring

通過簡單調(diào)試,很快就定位了高耗時的原因:

在 invokeBeanFactoryPostProcessors(beanFactory) 方法中,調(diào)用了所有注冊的 BeanFactory 的后置處理器;

其中,ConfigurationClassPostProcessor 這個后置處理器貢獻(xiàn)了大部分的耗時;

查閱相關(guān)資料,該后置處理器相當(dāng)重要,主要負(fù)責(zé)@Configuration、@ComponentScan、@Import、@Bean 等注解的解析;

繼續(xù)調(diào)試發(fā)現(xiàn),主要耗時都花在主配置類的 @ComponentScan 解析上,而且主要耗時還是在解析屬性 basePackages;

cda9fa54-b2c9-11ed-bfe3-dac502259ad0.jpg

basePackages

即項目主配置類上 @SpringBootApplication 注解的 scanBasePackages 屬性:

cdbacb4a-b2c9-11ed-bfe3-dac502259ad0.jpg

scanBasePackages

通過該方法 JavaDoc、查看相關(guān)代碼,大體了解到該過程是在遞歸掃描、解析 basePackages 所有路徑下的 class,對于可作為 Bean 的對象,生成其 BeanDefinition;如果遇到 @Configuration 注解的配置類,還得遞歸解析其 @ComponentScan。 至此,服務(wù)啟動緩慢的原因就找到了:

作為數(shù)據(jù)平臺,我們的服務(wù)引用了很多第三方依賴服務(wù),這些依賴往往提供了對應(yīng)業(yè)務(wù)的完整功能,所以提供的 jar 包非常大;

掃描這些包路徑下的 class 非常耗時,很多 class 都不提供 Bean,但還是花時間掃描了;

每添加一個服務(wù)的依賴,都會線性增加掃描的時間;

弄明白耗時的原因后,我有2個疑問:

是否所有的 class 都需要掃描,是否可以只掃描那些提供 Bean 的 class?

掃描出來的 Bean 是否都需要?我只接入一個功能,但是注入了所有的 Bean,這似乎不太合理?

監(jiān)控 Bean 注入耗時

第二個優(yōu)化的思路是監(jiān)控所有 Bean 對象初始化的耗時,即每個 Bean 對象實例化、初始化、注冊所花費(fèi)的時間,有沒有特別耗時 Bean 對象?同樣的,我們可以利用 SpringBoot 提供了 BeanPostProcessor 接口來監(jiān)控 Bean 的注入耗時,BeanPostProcessor 是 Spring 提供的 Bean 初始化前后的 IOC 鉤子,用于在 Bean 初始化的前后執(zhí)行一些自定義的邏輯:

publicinterfaceBeanPostProcessor{
//初始化前
defaultObjectpostProcessBeforeInitialization(Objectbean,StringbeanName)throwsBeansException{
returnbean;
}
//初始化后
defaultObjectpostProcessAfterInitialization(Objectbean,StringbeanName)throwsBeansException{
returnbean;
}
}

對于 BeanPostProcessor 接口的實現(xiàn)類,其前后置處理過程體現(xiàn)在 AbstractAutowireCapableBeanFactory#doCreateBean,這也是 Spring 中非常重要的一個方法,用于真正實例化 Bean 對象,通過 BeanFactory#getBean 方法一路 Debug 就能找到。在該方法中調(diào)用了 initializeBean 方法:

protectedObjectinitializeBean(StringbeanName,Objectbean,@NullableRootBeanDefinitionmbd){
...
ObjectwrappedBean=bean;
if(mbd==null||!mbd.isSynthetic()){
//應(yīng)用所有BeanPostProcessor的前置方法
wrappedBean=applyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);
}
try{
invokeInitMethods(beanName,wrappedBean,mbd);
}
catch(Throwableex){
thrownewBeanCreationException(
(mbd!=null?mbd.getResourceDescription():null),
beanName,"Invocationofinitmethodfailed",ex);
}
if(mbd==null||!mbd.isSynthetic()){
//應(yīng)用所有BeanPostProcessor的后置方法
wrappedBean=applyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);
}
returnwrappedBean;
}

通過 BeanPostProcessor 原理,在前置處理時記錄下當(dāng)前時間,在后置處理時,用當(dāng)前時間減去前置處理時間,就能知道每個 Bean 的初始化耗時,下面是我的實現(xiàn):

@Component
publicclassTimeCostBeanPostProcessorimplementsBeanPostProcessor{
privateMapcostMap=Maps.newConcurrentMap();

@Override
publicObjectpostProcessBeforeInitialization(Objectbean,StringbeanName)throwsBeansException{
costMap.put(beanName,System.currentTimeMillis());
returnbean;
}
@Override
publicObjectpostProcessAfterInitialization(Objectbean,StringbeanName)throwsBeansException{
if(costMap.containsKey(beanName)){
Longstart=costMap.get(beanName);
longcost=System.currentTimeMillis()-start;
if(cost>0){
costMap.put(beanName,cost);
System.out.println("bean:"+beanName+"	time:"+cost);
}
}
returnbean;
}
}

BeanPostProcessor 的邏輯是在 Beanfactory 準(zhǔn)備好后處理的,就不需要通過 SpringFactoriesLoader 加載了,直接 @Component 注入即可。

重啟服務(wù),通過以上方法排查 Bean 初始化過程,還真的有所發(fā)現(xiàn):

cddc1034-b2c9-11ed-bfe3-dac502259ad0.png

Bean 初始化過程

這個 Bean 初始化耗時43s,具體看下這個 Bean 的初始化方法,發(fā)現(xiàn)會從數(shù)據(jù)庫查詢大量配置元數(shù)據(jù),并更新到 Redis 緩存中,所以初始化非常慢:

cdf80186-b2c9-11ed-bfe3-dac502259ad0.jpg

初始化方法

另外,還發(fā)現(xiàn)了一些非項目自身服務(wù)的service、controller對象,這些 Bean 來自于第三方依賴:UPM服務(wù),項目中并不需要:

ce129636-b2c9-11ed-bfe3-dac502259ad0.jpg

第三方依賴

其實,原因上文已經(jīng)提到:我只接入一個功能,但我注入了該服務(wù)路徑下所有的 Bean,也就是說,服務(wù)里注入其他服務(wù)的、對自身無用的 Bean。

優(yōu)化方案

如何解決掃描路徑過多?

想到的解決方案比較簡單粗暴:梳理要引入的 Bean,刪掉主配置類上掃描路徑,使用 JavaConfig 的方式顯式手動注入。以 UPM 的依賴為例,「之前的注入方式」 是,項目依賴其 UpmResourceClient 對象,Pom 已經(jīng)引用了其 Maven 坐標(biāo),并在主配置類上的 scanBasePackages 中添加了其服務(wù)路徑:"com.xxx.ad.upm",通過掃描整個服務(wù)路徑下的 class,找到 UpmResourceClient 并注入,因為該類注解了 @Service,因此會注入到服務(wù)的 Spring 上下文中,UpmResourceClient 源碼片段及主配置類如下:

ce247bd0-b2c9-11ed-bfe3-dac502259ad0.jpg

UpmResourceClient

ce38bc4e-b2c9-11ed-bfe3-dac502259ad0.jpg

UpmResourceClient

使用 JavaConfig 的改造方式是:不再掃描 UPM 的服務(wù)路徑,而是主動注入。刪除"com.xxx.ad.upm",并在服務(wù)路徑下添加以下配置類:

@Configuration
publicclassThirdPartyBeanConfig{
@Bean
publicUpmResourceClientupmResourceClient(){
returnnewUpmResourceClient();
}
}

?

Tips:如果該 Bean 還依賴其他 Bean,則需要把所依賴的 Bean 都注入; 針對 Bean 依賴情況復(fù)雜的場景梳理起來就比較麻煩了,所幸項目用到的服務(wù) Bean 依賴關(guān)系都比較簡單,一些依賴關(guān)系復(fù)雜的服務(wù),觀察到其路徑掃描耗時也不是很高,就不處理了。

?

同時,通過 JavaConfig 按需注入的方式,就不存在冗余 Bean 的情況了,也有利于降低服務(wù)的內(nèi)存消耗;解決了上面的引入無關(guān)的 upmService、upmController 的問題。

如何解決 Bean 初始化高耗時?

Bean 初始化耗時高,就需要 case by case 地處理了,比如項目中遇到的初始化配置元數(shù)據(jù)的問題,可以考慮通過將該任務(wù)提交到線程池的方式異步處理或者懶加載的方式來解決。

新的問題

完成以上優(yōu)化后,本地啟動時間從之前的 7min 左右降低至 40s,效果還是非常顯著的。本地自測通過后,便發(fā)布到預(yù)發(fā)進(jìn)行驗證,驗證過程中,有同學(xué)發(fā)現(xiàn)項目接入的 Redis 緩存組件失效了。該組件接入方式與上文描述的接入方式類似,通過添加掃描服務(wù)的根路徑"com.xxx.ad.rediscache",注入對應(yīng)的 Bean 對象;查看該緩存組件項目的源碼,發(fā)現(xiàn)該路徑下有一個 config 類注入了一個緩存管理對象 CacheManager,其實現(xiàn)類是 RedisCacheManager:

ce5cf456-b2c9-11ed-bfe3-dac502259ad0.jpg

CacheManager

緩存組件代碼片段:

ce70ca8a-b2c9-11ed-bfe3-dac502259ad0.jpg

RedisCacheManager

本次優(yōu)化中,我是通過 「每次刪除一條掃描路徑,啟動服務(wù)后根據(jù)啟動日志中 Bean 缺失錯誤的信息,來逐個梳理、添加依賴的 Bean,保證服務(wù)正常啟動」 的方式來改造的,而刪除"com.xxx.ad.rediscache"后啟動服務(wù)并無異常,因此就沒有進(jìn)一步的操作,直接上預(yù)發(fā)驗證了。這就奇怪了,既然不掃描該組件的業(yè)務(wù)代碼根路徑,也就沒有執(zhí)行注入該組件中定義的 CacheManager 對象,為啥用到緩存的地方?jīng)]有報錯呢?

嘗試在未添加掃描路徑的情況下,從 ApplicationContext 中獲取 CacheManager 類型的對象看下是否存在?結(jié)果發(fā)現(xiàn)確實存在 RedisCacheManager 對象:

ce8a44d8-b2c9-11ed-bfe3-dac502259ad0.jpg

RedisCacheManager

其實,前面的分析并沒有錯,刪除掃描路徑后生成的 RedisCacheManager 并不是緩存組件代碼中配置的,而是 SpringBoot 的自動化配置生成的,也就是說該對象并不是我們想要的對象,是不符合預(yù)期的,下文介紹其原因。

SpringBoot 自動化裝配,讓人防不勝防

查閱 SpringBoot Cache 相關(guān)資料,發(fā)現(xiàn) SpringBoot Cache 做了一些自動推斷和注入的工作,原來是 SpringBoot 自動化裝配的鍋呀,接下來就分析下 SpringBoot Cache 原理,明確出現(xiàn)以上問題的原因。

SpringBoot 自動化配置,體現(xiàn)在主配置類上復(fù)合注解 @SpringBootApplication 中的@EnableAutoConfiguration 上,該注解開啟了 SpringBoot 的自動配置功能。該注解中的@Import(AutoConfigurationImportSelector.class) 通過加載 META-INF/spring.factotries 下配置一系列 *AutoConfiguration 配置類,根據(jù)現(xiàn)有條件推斷,盡可能地為我們配置需要的 Bean。這些配置類負(fù)責(zé)各個功能的自動化配置,其中用于 SpringBoot Cache 的自動配置類是 CacheAutoConfiguration,接下來重點(diǎn)分析這個配置類就行了。

cea1688e-b2c9-11ed-bfe3-dac502259ad0.jpg

CacheAutoConfiguration

?

@SpringBootApplication 復(fù)合注解中集成了三個非常重要的注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,其中 @EnableAutoConfiguration 就是負(fù)責(zé)開啟自動化配置功能;SpringBoot 中有多 @EnableXXX 的注解,都是用來開啟某一方面的功能,其實現(xiàn)原理也是類似的:通過 @Import 篩選、導(dǎo)入滿足條件的自動化配置類。

?

可以看到 CacheAutoConfiguration 上有許多注解,重點(diǎn)關(guān)注下@Import({CacheConfigurationImportSelector.class}),CacheConfigurationImportSelector 實現(xiàn)了 ImportSelector 接口,該接口用于動態(tài)選擇想導(dǎo)入的配置類,這個 CacheConfigurationImportSelector 用來導(dǎo)入不同類型的 Cache 的自動配置類:

ceb5c5a4-b2c9-11ed-bfe3-dac502259ad0.jpg

CacheConfigurationImportSelector

通過調(diào)試 CacheConfigurationImportSelector 發(fā)現(xiàn),根據(jù) SpringBoot 支持的緩存類型(CacheType),提供了10種 cache 的自動配置類,按優(yōu)先級排序,最終只有一個生效,而本項目中恰恰就是 RedisCacheConfiguration,其內(nèi)部提供的是 RedisCacheManager,和引入第三方緩存組件一樣,所以造成了困惑:

cecb6030-b2c9-11ed-bfe3-dac502259ad0.jpg

RedisCacheManager

看下 RedisCacheConfiguration 的實現(xiàn):

cee813a6-b2c9-11ed-bfe3-dac502259ad0.jpg

RedisCacheConfiguration

這個配置類上有很多條件注解,當(dāng)這些條件都滿足的話,這個自動配置類就會生效,而本項目恰恰都滿足,同時項目主配置類上還加上了 @EnableCaching,開啟了緩存功能,即使緩存組件沒生效,SpringBoot 也會自動生成一個緩存管理對象;

即:緩存組件服務(wù)掃描路徑存在的話,緩存組件中的代碼生成緩存管理對象,@ConditionalOnMissingBean(CacheManager.class) 失效;掃描路徑不存在的話,SpringBoot 通過推斷,自動生成一個緩存管理對象。

這個也很好驗證,在 RedisCacheConfiguration 中打斷點(diǎn),不刪除掃描路徑是走不到這邊的SpringBoot 自動裝配過程的(緩存組件顯式生成過了),刪除了掃描路徑是能走到的(SpringBoot 自動生成)。

?

上文多次提到@Import,這是 SpringBoot 中重要注解,主要有以下作用:1、導(dǎo)入 @Configuration 注解的類;2、導(dǎo)入實現(xiàn)了 ImportSelector 或 ImportBeanDefinitionRegistrar 的類;3、導(dǎo)入普通的 POJO。

?

使用 starter 機(jī)制,開箱即用

了解緩存失效的原因后,就有解決的辦法了,因為是自己團(tuán)隊的組件,就沒必要通過 JavaConfig 顯式手動導(dǎo)入的方式改造,而是通過 SpringBoot 的 starter 機(jī)制,優(yōu)化下緩存組件的實現(xiàn),可以做到自動注入、開箱即用。 只要改造下緩存組件的代碼,在 resources 文件中添加一個 META-INF/spring.factotries 文件,在下面配置一個 EnableAutoConfiguration 即可,這樣項目在啟動時也會掃描到這個 jar 中的 spring.factotries 文件,將 XxxAdCacheConfiguration 配置類自動引入,而不需要掃描"com.xxx.ad.rediscache"整個路徑了:

#EnableAutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.xxx.ad.rediscache.XxxAdCacheConfiguration






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    18927

    瀏覽量

    227247
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2943

    瀏覽量

    104107
  • Boot
    +關(guān)注

    關(guān)注

    0

    文章

    148

    瀏覽量

    35675

原文標(biāo)題:7min 到 40s:Spring Boot 啟動優(yōu)化實踐

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Spring Boot如何實現(xiàn)異步任務(wù)

    Spring Boot 提供了多種方式來實現(xiàn)異步任務(wù),這里介紹三種主要實現(xiàn)方式。 1、基于注解 @Async @Async 注解是 Spring 提供的一種輕量級異步方法實現(xiàn)方式,它可以標(biāo)記在方法上
    的頭像 發(fā)表于 09-30 10:32 ?1136次閱讀

    Spring Boot Starter需要些什么

    pulsar-spring-boot-starter是非常有必要的,在此之前,我們先看看一個starter需要些什么。 Spring Boot Starter spring-boot
    的頭像 發(fā)表于 09-25 11:35 ?633次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> Starter需要些什么

    啟動Spring Boot項目應(yīng)用的三種方法

    基礎(chǔ)。我們知道了Spring Boot是個什么了,那么我們又該如何啟動Spring Boot應(yīng)用呢?這里小編給大家推薦常用的三種方法。分別是
    發(fā)表于 01-14 17:33

    Spring Boot嵌入式Web容器原理是什么

    ,不需要配置任何特殊的XML配置,為了這個目標(biāo),Spring BootSpring 4.0框架之上提供了很多特性,幫助應(yīng)用以“約定優(yōu)于配置”“開箱即用”的方式來啟動應(yīng)用并運(yùn)行上下文。
    發(fā)表于 12-16 07:57

    Spring Boot定時任務(wù)的重寫方法

    Spring Boot應(yīng)該是目前最火的java開源框架了,它簡化了我們創(chuàng)建一個web服務(wù)的過程,讓我們可以在很短時間、基本零配置就可以啟動一個web服務(wù)。
    的頭像 發(fā)表于 01-20 17:38 ?2342次閱讀

    Spring Boot從零入門1 詳述

    在開始學(xué)習(xí)Spring Boot之前,我之前從未接觸過Spring相關(guān)的項目,Java基礎(chǔ)還是幾年前自學(xué)的,現(xiàn)在估計也忘得差不多了吧,寫Spring
    的頭像 發(fā)表于 12-10 22:18 ?544次閱讀

    Spring認(rèn)證」什么是Spring GraphQL?

    這個項目建立在 Boot 2.x 上,但它應(yīng)該與最新的 Boot2.4.x5 相關(guān)。 要創(chuàng)建項目,請轉(zhuǎn)到start.spring.io并為要使用的GraphQL傳輸選擇啟動器:
    的頭像 發(fā)表于 08-10 14:08 ?730次閱讀
    「<b class='flag-5'>Spring</b>認(rèn)證」什么是<b class='flag-5'>Spring</b> GraphQL?

    Spring Boot特有的實踐

    Spring Boot是最流行的用于開發(fā)微服務(wù)的Java框架。在本文中,我將與你分享自2016年以來我在專業(yè)開發(fā)中使用Spring Boot所采用的最佳
    的頭像 發(fā)表于 09-29 10:24 ?803次閱讀

    強(qiáng)大的Spring Boot 3.0要來了

    來源:OSC開源社區(qū)(ID:oschina2013) Spring Boot 3.0 首個 RC 已發(fā)布,此外還為兩個分支發(fā)布了更新:2.7.5 2.6.13。 3.0.0-RC1: https
    的頭像 發(fā)表于 10-31 11:17 ?1423次閱讀

    怎樣使用Kiuwan保護(hù)Spring Boot應(yīng)用程序呢?

    Spring Boot 提供了快速輕松地構(gòu)建基于Spring 的應(yīng)用程序所需的工具、功能和依賴項。
    的頭像 發(fā)表于 03-16 09:10 ?682次閱讀

    Spring Boot Web相關(guān)的基礎(chǔ)知識

    上一篇文章我們已經(jīng)學(xué)會了如何通過IDEA快速建立一個Spring Boot項目,還介紹了Spring Boot項目的結(jié)構(gòu),介紹了項目配置文件pom.xml的組成部分,并且撰寫了我們
    的頭像 發(fā)表于 03-17 15:03 ?564次閱讀

    Spring Boot Actuator快速入門

    不知道大家在寫 Spring Boot 項目的過程中,使用過 Spring Boot Actuator 嗎?知道 Spring
    的頭像 發(fā)表于 10-09 17:11 ?528次閱讀

    Spring Boot啟動 Eureka流程

    在上篇中已經(jīng)說過了 Eureka-Server 本質(zhì)上是一個 web 應(yīng)用的項目,今天就來看看 Spring Boot 是怎么啟動 Eureka 的。 Spring
    的頭像 發(fā)表于 10-10 11:40 ?709次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b><b class='flag-5'>啟動</b> Eureka流程

    Spring Boot啟動原理

    來指定依賴,才能夠運(yùn)行。我們今天就來分析講解一下 Spring Boot啟動原理。 1. Spring Boot 打包插件
    的頭像 發(fā)表于 10-13 11:44 ?542次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>的<b class='flag-5'>啟動</b>原理

    Spring Boot 的設(shè)計目標(biāo)

    什么是Spring Boot Spring BootSpring 開源組織下的一個子項目,也是 S
    的頭像 發(fā)表于 10-13 14:56 ?488次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> 的設(shè)計目標(biāo)