99国产精品欲av蜜臀,可以直接免费观看的AV网站,gogogo高清免费完整版,啊灬啊灬啊灬免费毛片

網易首頁 > 網易號 > 正文 申請入駐

公司入職一個阿里大佬,把 Spring Boot 系統啟動時間從 7 分鐘降到了 40 秒!

0
分享至

來自:掘金,作者:Debugger 鏈接:https://juejin.cn/post/7181342523728592955
0、背景

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

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

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

  • 基于 BeanPostProcessor 原理監控 Bean 注入耗時;

  • SpringBoot Cache 自動化配置原理;

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


1、耗時問題排查

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

  1. 排查 SpringBoot 服務的啟動過程;

  2. 排查 Bean 的初始化耗時;

1.1 觀察 SpringBoot 啟動 run 方法

該項目使用基于 SpringBoot 改造的內部微服務組件 XxBoot 作為服務端實現,其啟動流程與 SpringBoot 類似,分為ApplicationContext構造和ApplicationContext啟動兩部分,即通過構造函數實例化ApplicationContext對象,并調用其run方法啟動服務:

public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);

public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

ApplicationContext對象構造過程,主要做了自定義 Banner 設置、應用類型推斷、配置源設置等工作,不做特殊擴展的話,大部分項目都是差不多的,不太可能引起耗時問題。通過在run方法中打斷點,啟動后很快就運行到斷點位置,也能驗證這一點。

接下就是重點排查run方法的啟動過程中有哪些性能瓶頸?SpringBoot 的啟動過程非常復雜,慶幸的是 SpringBoot 本身提供的一些機制,將 SpringBoot 的啟動過程劃分了多個階段,這個階段劃分的過程就體現在SpringApplicationRunListener接口中,該接口將ApplicationContext對象的run方法劃分成不同的階段:

public interface SpringApplicationRunListener {
// run 方法第一次被執行時調用,早期初始化工作
void starting();
// environment 創建后,ApplicationContext 創建前
void environmentPrepared(ConfigurableEnvironment environment);
// ApplicationContext 實例創建,部分屬性設置了
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext 加載后,refresh 前
void contextLoaded(ConfigurableApplicationContext context);
// refresh 后
void started(ConfigurableApplicationContext context);
// 所有初始化完成后,run 結束前
void running(ConfigurableApplicationContext context);
// 初始化失敗后
void failed(ConfigurableApplicationContext context, Throwable exception);

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

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

這里我們不必深究實現類EventPublishingRunListener的功能,但是可以通過SpringApplicationRunListener原理,添加一個自定義的實現類,在不同階段結束時打印下當前時間,通過計算不同階段的運行時間,就能大體定位哪些階段耗時比較高,然后重點排查這些階段的代碼。

先看下SpringApplicationRunListener的實現原理,其劃分不同階段的邏輯體現在ApplicationContextrun方法中:

public ConfigurableApplicationContext run(String... args) {
// 加載所有 SpringApplicationRunListener 的實現類
SpringApplicationRunListeners listeners = getRunListeners(args);
// 調用了 starting
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 調用了 environmentPrepared
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
// 內部調用了 contextPrepared、contextLoaded
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
// 調用了 started
listeners.started(context);
callRunners(context, applicationArguments);
catch (Throwable ex) {
// 內部調用了 failed
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
try {
// 調用了 running
listeners.running(context);
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
return context;

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

因此,只要編寫一個SpringApplicationRunListener的自定義實現類,在實現接口不同階段方法時,打印當前時間;并在META-INF/spring.factotries中配置該類后,該類也會實例化,存到listeners中;在不同階段結束時打印結束時間,以此來評估不同階段的執行耗時。

在項目中添加實現類MySpringApplicationRunListener

@Slf4j
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
// 這個構造函數不能少,否則反射生成實例會報錯
public MySpringApplicationRunListener(SpringApplication sa, String[] args) {
@Override
public void starting() {
log.info("starting {}", LocalDateTime.now());
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
log.info("environmentPrepared {}", LocalDateTime.now());
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
log.info("contextPrepared {}", LocalDateTime.now());
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
log.info("contextLoaded {}", LocalDateTime.now());
@Override
public void started(ConfigurableApplicationContext context) {
log.info("started {}", LocalDateTime.now());
@Override
public void running(ConfigurableApplicationContext context) {
log.info("running {}", LocalDateTime.now());
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
log.info("failed {}", LocalDateTime.now());

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

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

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.xxx.ad.diagnostic.tools.api.MySpringApplicationRunListener

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

重啟服務,觀察MySpringApplicationRunListener的日志輸出,發現主要耗時都在contextLoadedstarted兩個階段之間,在這兩個階段之間調用了2個方法:refreshContextafterRefresh方法,而refreshContext底層調用的是AbstractApplicationContext#refresh,Spring 初始化 context 的核心方法之一就是這個refresh

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


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

  1. invokeBeanFactoryPostProcessors(beanFactory)方法中,調用了所有注冊的BeanFactory的后置處理器;

  2. 其中,ConfigurationClassPostProcessor這個后置處理器貢獻了大部分的耗時;

  3. 查閱相關資料,該后置處理器相當重要,主要負責@Configuration@ComponentScan@Import@Bean等注解的解析;

  4. 繼續調試發現,主要耗時都花在主配置類的@ComponentScan解析上,而且主要耗時還是在解析屬性basePackages


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


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

  1. 作為數據平臺,我們的服務引用了很多第三方依賴服務,這些依賴往往提供了對應業務的完整功能,所以提供的 jar 包非常大;

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

  3. 每添加一個服務的依賴,都會線性增加掃描的時間;

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

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

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

1.2 監控 Bean 注入耗時

第二個優化的思路是監控所有 Bean 對象初始化的耗時,即每個 Bean 對象實例化、初始化、注冊所花費的時間,有沒有特別耗時 Bean 對象?

同樣的,我們可以利用 SpringBoot 提供了BeanPostProcessor接口來監控 Bean 的注入耗時,BeanPostProcessor是 Spring 提供的 Bean 初始化前后的 IOC 鉤子,用于在 Bean 初始化的前后執行一些自定義的邏輯:

public interface BeanPostProcessor {
// 初始化前
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
// 初始化后
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;

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

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 應用所有 BeanPostProcessor 的前置方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
try {
invokeInitMethods(beanName, wrappedBean, mbd);
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
if (mbd == null || !mbd.isSynthetic()) {
// 應用所有 BeanPostProcessor 的后置方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;

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

@Component
public class TimeCostBeanPostProcessor implements BeanPostProcessor {
private Map costMap = Maps.newConcurrentMap();

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
costMap.put(beanName, System.currentTimeMillis());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (costMap.containsKey(beanName)) {
Long start = costMap.get(beanName);
long cost = System.currentTimeMillis() - start;
if (cost > 0) {
costMap.put(beanName, cost);
System.out.println("bean: " + beanName + "\ttime: " + cost);
}
}
return bean;
}
}

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

重啟服務,通過以上方法排查 Bean 初始化過程,還真的有所發現:

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


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


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


2、優化方案
2.1 如何解決掃描路徑過多?

想到的解決方案比較簡單粗暴:

梳理要引入的 Bean,刪掉主配置類上掃描路徑,使用 JavaConfig 的方式顯式手動注入。

以 UPM 的依賴為例,之前的注入方式是,項目依賴其 UpmResourceClient 對象,Pom 已經引用了其 Maven 坐標,并在主配置類上的scanBasePackages中添加了其服務路徑:"com.xxx.ad.upm",通過掃描整個服務路徑下的 class,找到 UpmResourceClient 并注入,因為該類注解了@Service,因此會注入到服務的 Spring 上下文中,UpmResourceClient 源碼片段及主配置類如下:



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

@Configuration
public class ThirdPartyBeanConfig {
@Bean
public UpmResourceClient upmResourceClient() {
return new UpmResourceClient();

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

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

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

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


3、新的問題

完成以上優化后,本地啟動時間從之前的 7min 左右降低至 40s,效果還是非常顯著的。本地自測通過后,便發布到預發進行驗證,驗證過程中,有同學發現項目接入的 Redis 緩存組件失效了。

該組件接入方式與上文描述的接入方式類似,通過添加掃描服務的根路徑"com.xxx.ad.rediscache",注入對應的 Bean 對象;查看該緩存組件項目的源碼,發現該路徑下有一個 config 類注入了一個緩存管理對象CacheManager,其實現類是RedisCacheManager


緩存組件代碼片段:


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

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


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

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

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

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


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

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


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


看下RedisCacheConfiguration的實現:


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

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

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

上文多次提到@Import,這是 SpringBoot 中重要注解,主要有以下作用:1、導入 @Configuration 注解的類;2、導入實現了 ImportSelector 或 ImportBeanDefinitionRegistrar 的類;3、導入普通的 POJO。
3.2 使用 starter 機制,開箱即用

了解緩存失效的原因后,就有解決的辦法了,因為是自己團隊的組件,就沒必要通過 JavaConfig 顯式手動導入的方式改造,而是通過 SpringBoot 的 starter 機制,優化下緩存組件的實現,可以做到自動注入、開箱即用。

只要改造下緩存組件的代碼,在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

SpringBoot 的 EnableAutoConfiguration 自動配置原理還是比較復雜的,在加載自動配置類前還要先加載自動配置的元數據,對所有自動配置類做有效性篩選,具體可查閱 EnableAutoConfiguration 相關代碼;

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
留學圈的話到底有多炸裂?網友:在日本生活5年,我陪她墮了3次胎

留學圈的話到底有多炸裂?網友:在日本生活5年,我陪她墮了3次胎

解讀熱點事件
2025-07-23 00:10:03
易中天:不殺,留著終是個危險

易中天:不殺,留著終是個危險

尚曦讀史
2025-07-23 01:45:02
特朗普這話一出,菲律賓總統臉色變了....

特朗普這話一出,菲律賓總統臉色變了....

環球時報新聞
2025-07-23 11:38:05
曝山東男籃簽下奧拉迪波,邱彪這是在干啥?難怪楊鳴出言不遜

曝山東男籃簽下奧拉迪波,邱彪這是在干啥?難怪楊鳴出言不遜

姜大叔侃球
2025-07-23 16:14:29
2025年高考分數線猛跌的4所211大學,400多分成功撿漏,實屬罕見

2025年高考分數線猛跌的4所211大學,400多分成功撿漏,實屬罕見

教育導向分享
2025-07-22 19:25:20
我是正師級軍官,參加同學聚會被初戀嘲笑,第二年我轉業任副市長

我是正師級軍官,參加同學聚會被初戀嘲笑,第二年我轉業任副市長

喬生桂
2025-07-22 17:09:49
涼爽倒計時!湖北連發38條預警,即將重回40℃!

涼爽倒計時!湖北連發38條預警,即將重回40℃!

極目新聞
2025-07-23 12:31:27
首次,2名中國大陸出身的華人當選為日本參議院議員

首次,2名中國大陸出身的華人當選為日本參議院議員

徐靜波靜說日本
2025-07-23 07:24:02
梁朝偉和湯唯在《色戒》里“假戲真做”?網友爆出截圖:一目了然

梁朝偉和湯唯在《色戒》里“假戲真做”?網友爆出截圖:一目了然

姜糖先生
2025-06-08 19:31:00
央視曝光!你以為沒壞實際早已“細菌爆表”的5種食物,趕緊扔了

央視曝光!你以為沒壞實際早已“細菌爆表”的5種食物,趕緊扔了

阿傖說事
2025-07-22 08:00:09
二手房拋售狂潮席卷全國:我們制造史詩級變革?將會有什么結果

二手房拋售狂潮席卷全國:我們制造史詩級變革?將會有什么結果

小白鴿財經
2025-07-21 08:36:15
“絕經和出道同時來?”上海街頭驚現她的巨幅海報!網友:笑著笑著就哭了

“絕經和出道同時來?”上海街頭驚現她的巨幅海報!網友:笑著笑著就哭了

環球網資訊
2025-07-23 10:48:19
免費領雞蛋,騙244億養老錢!中國老年人“第一大忽悠”終于倒了

免費領雞蛋,騙244億養老錢!中國老年人“第一大忽悠”終于倒了

新語愛八卦
2025-07-22 17:59:09
5年7500萬!曝比亞迪已暫停贊助國足:因大量球迷抵制國足贊助商

5年7500萬!曝比亞迪已暫停贊助國足:因大量球迷抵制國足贊助商

風過鄉
2025-07-22 20:57:26
實錘?網傳杜建英有丈夫,還有一個36歲大兒子,宗慶后竟然是三哥

實錘?網傳杜建英有丈夫,還有一個36歲大兒子,宗慶后竟然是三哥

壹月情感
2025-07-20 22:06:40
NBA歷史僅10人能在出戰400+場比賽保持70+%勝率 小卡是現役唯一

NBA歷史僅10人能在出戰400+場比賽保持70+%勝率 小卡是現役唯一

直播吧
2025-07-23 19:09:16
宗馥莉叔叔宗澤后接受巴倫中文網獨家采訪,又爆出猛料,令人深思

宗馥莉叔叔宗澤后接受巴倫中文網獨家采訪,又爆出猛料,令人深思

悠閑歷史
2025-07-23 15:55:02
北京化工大學碳纖維錄取通知書能切西瓜,學校招生辦:本科生專屬,明年不再沿用

北京化工大學碳纖維錄取通知書能切西瓜,學校招生辦:本科生專屬,明年不再沿用

極目新聞
2025-07-23 13:36:32
宮魯鳴或卸任,女籃新帥或敲定,62歲,名宿,或成李夢回歸關鍵

宮魯鳴或卸任,女籃新帥或敲定,62歲,名宿,或成李夢回歸關鍵

東球弟
2025-07-23 11:10:02
央視曝光!又一灰色產業鏈暴雷!0成本套現48萬,還不用還?

央視曝光!又一灰色產業鏈暴雷!0成本套現48萬,還不用還?

大魚簡科
2025-07-23 16:17:38
2025-07-23 20:08:49
Meta
Meta
關注java進階架構師送架構
1059文章數 9856關注度
往期回顧 全部

科技要聞

別自嗨了!XREAL徐馳:AI眼鏡只有5歲智商

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

頭條要聞

印度、孟加拉關切雅魯藏布江下游水電站工程 中方回應

體育要聞

英格蘭最紅球星 也是加勒比島國驕傲

娛樂要聞

汪峰森林北同游日本 各帶各娃互不耽誤

財經要聞

律師解析娃哈哈遺產案:遺囑是最大變數

汽車要聞

德系大招放盡 場地極限測試全新奧迪A5L

態度原創

數碼
旅游
本地
房產
家居

數碼要聞

全漢帶來 VIC GD 系列電源:僅擁有 3 年質保的金牌非模組 ATX

旅游要聞

熱聞|清明假期將至,熱門目的地有哪些?

本地新聞

這雙丑鞋“泰”辣眼,跪求內娛不要抄作業

房產要聞

海南自由貿易港全島封關,2025年12月18日正式啟動!

家居要聞

晨曦生活 明媚而放松

無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 永修县| 兴安盟| 荆门市| 双流县| 墨江| 桐乡市| 商城县| 娄烦县| 赤水市| 永德县| 海城市| 长海县| 清苑县| 娄烦县| 新营市| 阳曲县| 海兴县| 邮箱| 庄河市| 屯留县| 涿鹿县| 扎兰屯市| 农安县| 凤城市| 湾仔区| 都江堰市| 科技| 泰来县| 剑川县| 策勒县| 玉树县| 息烽县| 安溪县| 成都市| 罗源县| 高尔夫| 平罗县| 通道| 景泰县| 大渡口区| 宁南县|