【SpringBoot系列-01】Spring Boot 啟動原理深度解析

【SpringBoot系列-01】Spring Boot 啟動原理深度解析

大家好!今天咱們來好好聊聊Spring Boot的啟動原理。估計不少人跟我一樣,剛開始用Spring Boot的時候覺得這玩意兒真神奇,一個main方法跑起來就啥都有了。但時間長了總會好奇:這背后到底發生了啥?

1. 啟動流程源碼分析

咱們先從最熟悉的入口開始,就是那個帶著@SpringBootApplication注解的main方法:

@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {// 這句就是啟動的核心,咱們今天就圍著它轉SpringApplication.run(DemoApplication.class, args);}
}

就這么一行代碼,背后卻藏著大學問。咱們先來看個整體的流程圖,有個宏觀認識:

main方法
創建SpringApplication實例
調用run方法
準備環境Environment
創建ApplicationContext
預處理上下文
刷新上下文refresh
刷新后處理
觸發運行時處理器
啟動完成

run()方法里的關鍵步驟

咱們直接看SpringApplication.run()方法的源碼(基于2.7.x版本):

public ConfigurableApplicationContext run(String... args) {// 計時器,記錄啟動時間,調試時很有用StopWatch stopWatch = new StopWatch();stopWatch.start();// 初始化應用上下文和異常報告器ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 配置headless模式,一般用于服務器環境,不需要顯示器等外設configureHeadlessProperty();// 第一步:獲取并啟動監聽器SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {// 第二步:準備應用參數ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 第三步:準備環境(重點!)ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 打印Banner,就是啟動時那個Spring的logoBanner printedBanner = printBanner(environment);// 第四步:創建應用上下文(重點!)context = createApplicationContext();// 第五步:準備異常報告器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 第六步:預處理上下文(重點!)prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 第七步:刷新上下文(最核心!)refreshContext(context);// 第八步:刷新后的處理afterRefresh(context, applicationArguments);// 停止計時器stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 通知監聽器啟動完成listeners.started(context);// 第九步:執行 runnerscallRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}// 返回上下文return context;
}

各步驟詳細解析

Main方法SpringApplicationListenersEnvironmentApplicationContextBeansSpringApplication.run()創建SpringApplication實例獲取并啟動監聽器starting事件prepareEnvironment()environmentPrepared事件createApplicationContext()prepareContext()contextPrepared事件refreshContext()掃描和加載BeancontextRefreshed事件started事件callRunners()running事件返回ApplicationContextMain方法SpringApplicationListenersEnvironmentApplicationContextBeans

這段代碼雖然長,但邏輯很清晰。我給你們劃幾個重點:

  1. 環境準備(prepareEnvironment):這里會加載各種配置,包括application.properties、系統變量、命令行參數等。調試時可以看這里加載了哪些配置源。

  2. 創建應用上下文(createApplicationContext):根據應用類型(Servlet/Reactive/None)創建不同的上下文。這里有個小技巧,你調試時注意看ApplicationContext的具體實現類,Web應用一般是AnnotationConfigServletWebServerApplicationContext

  3. 預處理上下文(prepareContext):這里會加載咱們的主配置類(就是帶@SpringBootApplication的那個類)。

  4. 刷新上下文(refreshContext):這是最核心的一步,里面會完成Bean的掃描、創建、依賴注入等一系列操作。Spring的IoC容器就是在這里真正工作的。

  5. 執行runners:這是啟動完成前的最后一步,咱們可以在這里做一些初始化工作。

我踩過一個坑,就是在項目啟動慢的時候,不知道哪里出了問題。后來就是在run()方法里打了斷點,一步步看哪個階段耗時最長,最后發現是某個配置類加載了太多不必要的Bean。所以說,熟悉這個流程對排查問題非常有幫助。

2. SpringApplication初始化過程

咱們剛才看了run()方法的流程,但在調用run()之前,SpringApplication實例的創建也很關鍵。咱們來看它的構造器:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;// 斷言主源不能為null,否則啟動不了Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 第一步:判斷應用類型this.webApplicationType = WebApplicationType.deduceFromClasspath();// 第二步:加載初始化器this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 第三步:加載監聽器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 第四步:推斷主應用類(就是咱們寫main方法的那個類)this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication初始化流程圖

有Servlet類
有Reactive類
都沒有
SpringApplication構造器
判斷應用類型
類路徑檢查
SERVLET類型
REACTIVE類型
NONE類型
加載初始化器
從spring.factories加載
加載監聽器
推斷主應用類
初始化完成

為什么要判斷webApplicationType?

這個判斷太重要了!WebApplicationType.deduceFromClasspath()會根據類路徑上的類來判斷應用類型:

  • SERVLET:如果有Servlet相關類且沒有WebFlux相關類,就是普通的Spring MVC應用
  • REACTIVE:如果有WebFlux相關類且沒有Servlet相關類,就是響應式應用
  • NONE:都沒有,就是普通的非Web應用

這直接決定了后面創建什么樣的ApplicationContext和嵌入式服務器。比如Web應用會創建TomcatServletWebServerFactory,而非Web應用就不會。

實際開發中,有時候你明明想創建一個非Web應用,卻因為引入了spring-boot-starter-web依賴,導致它變成了Web應用,啟動時會自動啟動Tomcat。這時候你就可以在啟動類里手動設置:

public static void main(String[] args) {new SpringApplicationBuilder(DemoApplication.class).web(WebApplicationType.NONE) // 強制非Web應用.run(args);
}

初始化器和監聽器是怎么被加載的

注意構造器里的getSpringFactoriesInstances()方法,這是Spring Boot的一個核心機制。它會去掃描所有jar包下的META-INF/spring.factories文件,加載里面配置的類。

比如ApplicationContextInitializer的加載,就是讀取spring.factories中key為org.springframework.context.ApplicationContextInitializer的配置。

咱們自己寫starter的時候,也經常用這招。比如想自動注冊一些組件,就可以在自己的starter里放一個spring.factories文件,配置上需要自動加載的類。

給你們看個小demo,自定義一個初始化器:

// 自定義初始化器
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("自定義初始化器執行了!");// 可以在這里做一些早期的配置ConfigurableEnvironment environment = applicationContext.getEnvironment();environment.setActiveProfiles("dev"); // 比如強制設置激活的環境}
}

然后在resources下創建META-INF/spring.factories

org.springframework.context.ApplicationContextInitializer=\
com.example.demo.MyInitializer

這樣啟動的時候,咱們的初始化器就會被自動加載執行了。是不是很簡單?這招在開發中間件或者通用組件時特別有用。

3. 事件監聽機制與啟動階段劃分

Spring Boot在啟動過程中會觸發一系列事件,這些事件可以幫助我們在不同階段做一些自定義操作。咱們先來看一張表格,了解下主要的事件及其觸發時機:

事件類型觸發時機主要用途
ApplicationStartingEvent剛調用run()方法時,在任何處理之前最早的事件,可用于初始化一些非常早期的資源
ApplicationEnvironmentPreparedEvent環境準備完成,但上下文還沒創建可以修改環境變量,比如添加額外的配置
ApplicationContextInitializedEvent上下文創建并初始化,但Bean定義還沒加載可以對上下文做一些設置
ApplicationPreparedEvent上下文準備完成,但還沒刷新可以在Bean加載前做一些操作
ApplicationStartedEvent上下文刷新完成,Bean已加載,但runner還沒執行可以做一些啟動后的準備工作,如緩存預熱
ApplicationReadyEvent所有啟動過程完成,應用已可以處理請求通知應用已就緒
ApplicationFailedEvent啟動失敗時處理啟動失敗的情況,如資源清理

事件觸發流程圖

run()方法調用
環境準備
上下文初始化
上下文準備
刷新上下文
啟動完成
Runners執行完成
應用就緒
失敗
失敗
失敗
失敗
失敗
失敗
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ContextRefreshed
ApplicationStartedEvent
ApplicationReadyEvent
ApplicationFailedEvent

這些事件都是通過SpringApplicationRunListeners來傳播的。咱們來寫個監聽器的demo,感受一下:

// 監聽啟動完成事件
@Component
public class MyStartupListener implements ApplicationListener<ApplicationStartedEvent> {@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {System.out.println("應用啟動完成,開始預熱緩存...");// 模擬緩存預熱CacheManager cacheManager = event.getApplicationContext().getBean(CacheManager.class);Cache userCache = cacheManager.getCache("userCache");// 預熱一些常用數據userCache.put(1L, new User(1L, "admin"));System.out.println("緩存預熱完成!");}
}// 緩存配置
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic CacheManager cacheManager() {return new ConcurrentMapCacheManager("userCache");}
}// User類
public class User {private Long id;private String name;public User(Long id, String name) {this.id = id;this.name = name;}// getter和setter方法public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

這個例子中,我們在應用啟動完成后,預熱了用戶緩存,這樣用戶第一次訪問時就不用等數據庫查詢了。這在實際項目中是個很常見的優化手段。

另外,還有個小技巧:如果你的監聽器需要排序執行,可以實現Ordered接口或者加上@Order注解。

4. Bean定義加載過程

Bean的加載可以說是Spring的靈魂了,咱們來看看Spring Boot是怎么加載Bean定義的。

Bean加載流程圖

注解類型
注解類型
注解類型
注解類型
@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan
@SpringBootConfiguration
掃描當前包及子包
查找組件注解
注冊為BeanDefinition
AutoConfigurationImportSelector
加載spring.factories
獲取所有自動配置類
根據@Conditional條件過濾
注冊符合條件的配置類
@Component
@Service
@Repository
@Controller
BeanDefinitionRegistry
創建Bean實例
依賴注入
初始化完成

@ComponentScan的掃描邏輯

@SpringBootApplication注解里包含了@ComponentScan,它會掃描指定包下的類,把帶有@Component@Service@Repository@Controller等注解的類注冊為Bean。

咱們來看下它的核心邏輯(簡化版):

// ComponentScanAnnotationParser的parse方法
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {// 創建掃描器ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);// ... 省略部分代碼 ...// 配置包含過濾器for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}// 配置排除過濾器(重點注意!)for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}// 配置掃描的包Set<String> basePackages = new LinkedHashSet<>();String[] basePackagesArray = componentScan.getStringArray("basePackages");for (String pkg : basePackagesArray) {String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);Collections.addAll(basePackages, tokenized);}// 如果沒有指定包,默認使用@ComponentScan所在類的包if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(declaringClass));}// 開始掃描并注冊Bean定義return scanner.doScan(StringUtils.toStringArray(basePackages));
}

這里有個地方要特別注意:excludeFilters會過濾掉某些類。默認情況下,Spring Boot會排除一些特定的類,比如帶有@ConditionalOnMissingBean等條件注解且條件不滿足的類。

實際開發中,有時候你會發現明明加了@Service注解的類,卻沒有被注冊為Bean,這時候就要檢查:

  1. 是不是包掃描路徑不對
  2. 是不是被某個過濾器排除了
  3. 是不是有條件注解沒滿足

可以在scanner.doScan()這里打個斷點,看看掃描結果里有沒有你的類。

自動配置類是怎么被加載的

Spring Boot的自動配置是它最強大的功能之一,這得益于@EnableAutoConfiguration注解。咱們來看它的源碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

關鍵就在@Import(AutoConfigurationImportSelector.class),這個類會幫我們導入所有符合條件的自動配置類。

自動配置加載流程

@SpringBootApplication@EnableAutoConfigurationAutoConfigurationImportSelectorspring.factories@ConditionalBeanDefinitionRegistry包含注解@Import導入加載META-INF/spring.factories返回所有自動配置類列表去重和排序檢查@Conditional條件true注冊配置類false跳過該配置類alt[條件滿足][條件不滿足]loop[每個自動配置類]自動配置完成@SpringBootApplication@EnableAutoConfigurationAutoConfigurationImportSelectorspring.factories@ConditionalBeanDefinitionRegistry

AutoConfigurationImportSelector的核心方法是selectImports(),它會從META-INF/spring.factories中加載所有配置的自動配置類(key為org.springframework.boot.autoconfigure.EnableAutoConfiguration),然后根據條件注解(@Conditional)篩選出符合條件的配置類。

咱們自己寫starter的時候,就是通過這種方式來實現自動配置的。比如mybatis-spring-boot-starter里就有MybatisAutoConfiguration這個自動配置類。

為什么@Configuration注解不能少

為什么必須加這個注解呢?因為Spring在處理@Configuration注解的類時,會通過CGLIB為它創建一個代理對象,這個代理會負責處理@Bean方法之間的依賴關系,確保Bean的單例性。

舉個例子:

// 正確的配置類
@Configuration
public class AppConfig {@Beanpublic ServiceA serviceA() {return new ServiceA();}@Beanpublic ServiceB serviceB() {// 這里會調用serviceA()方法return new ServiceB(serviceA());}
}// 如果不加@Configuration(錯誤示例)
public class AppConfig {@Beanpublic ServiceA serviceA() {return new ServiceA();}@Beanpublic ServiceB serviceB() {// 每次調用serviceA()都會創建新實例!return new ServiceB(serviceA());}
}

如果加了@Configuration,不管調用多少次serviceA(),返回的都是同一個實例(代理會從容器中獲取)。但如果沒加,每次調用都會創建一個新實例,這就違反了Spring的單例原則,可能會導致各種奇怪的問題。

所以記住,配置類一定要加@Configuration注解,別偷懶!

5. 啟動擴展點詳解

Spring Boot提供了很多擴展點,讓我們可以在啟動過程中插入自己的邏輯。咱們來講幾個常用的。

擴展點執行順序圖

ApplicationContextInitializer
BeanDefinitionRegistryPostProcessor
BeanFactoryPostProcessor
BeanPostProcessor-before
PostConstruct注解
InitializingBean
init-method
BeanPostProcessor-after
ApplicationListener
CommandLineRunner/ApplicationRunner

CommandLineRunner和ApplicationRunner的區別

這兩個接口都可以用來在應用啟動后執行一些操作,它們的區別主要在參數上:

// CommandLineRunner接收原始的命令行參數
@Component
@Order(2) // 執行順序
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("CommandLineRunner執行,參數:" + Arrays.toString(args));// args就是main方法接收的參數數組}
}// ApplicationRunner接收解析后的命令行參數
@Component
@Order(1) // 可以指定執行順序,數字越小越先執行
public class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("ApplicationRunner執行");System.out.println("選項參數:" + args.getOptionNames());System.out.println("非選項參數:" + args.getNonOptionArgs());// 獲取特定選項的值if (args.containsOption("debug")) {System.out.println("Debug模式已開啟");}}
}

使用場景建議:

  • 如果只是簡單地需要命令行參數,用CommandLineRunner更簡單
  • 如果需要處理復雜的命令行參數(特別是選項參數),用ApplicationRunner更方便
  • 可以通過@Order注解指定多個runner的執行順序

BeanPostProcessor的作用

BeanPostProcessor是Spring中非常強大的一個擴展點,它可以在Bean初始化前后對Bean進行處理。咱們常用的@Autowired、@Value等注解,都是靠它來實現的。

來看個實用的例子:

@Component
public class PerformanceMonitorBeanPostProcessor implements BeanPostProcessor {private Map<String, Long> beanInitTimes = new HashMap<>();// Bean初始化前調用@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 記錄初始化開始時間beanInitTimes.put(beanName, System.currentTimeMillis());return bean;}// Bean初始化后調用@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {Long startTime = beanInitTimes.get(beanName);if (startTime != null) {long initTime = System.currentTimeMillis() - startTime;if (initTime > 100) { // 超過100ms的打印警告System.out.println("警告:Bean [" + beanName + "] 初始化耗時:" + initTime + "ms");}beanInitTimes.remove(beanName);}return bean;}
}

這個例子展示了如何監控Bean的初始化耗時,對于排查啟動慢的問題非常有用。

Spring中的AutowiredAnnotationBeanPostProcessor就是用來處理@Autowired注解的,它會在Bean初始化前掃描Bean中的@Autowired注解,然后自動注入依賴。

不過要注意,BeanPostProcessor本身也是Bean,所以定義它的時候不能依賴其他Bean的初始化,否則可能會導致循環依賴問題。

自定義ApplicationContextInitializer

ApplicationContextInitializer是在Spring上下文初始化之前執行的,它可以用來對上下文進行一些配置。在做中間件適配時特別有用,比如需要統一設置一些上下文屬性。

實現方式很簡單:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {// 設置一些系統屬性System.setProperty("spring.profiles.default", "dev");// 添加一些自定義的環境變量ConfigurableEnvironment environment = applicationContext.getEnvironment();Map<String, Object> myProps = new HashMap<>();myProps.put("myapp.version", "1.0.0");myProps.put("myapp.name", "Demo Application");environment.getPropertySources().addLast(new MapPropertySource("myProps", myProps));// 注冊一個BeanFactoryPostProcessorapplicationContext.addBeanFactoryPostProcessor(beanFactory -> {System.out.println("Bean定義數量:" + beanFactory.getBeanDefinitionCount());});System.out.println("自定義ApplicationContextInitializer執行完成");}
}

然后在spring.factories中注冊:

org.springframework.context.ApplicationContextInitializer=\
com.example.demo.MyApplicationContextInitializer

或者在啟動類中直接注冊:

public static void main(String[] args) {new SpringApplicationBuilder(DemoApplication.class).initializers(new MyApplicationContextInitializer()).run(args);
}

這種方式比監聽器更早執行,適合做一些最早期的配置工作。

6. 常見問題與調試技巧

啟動慢的排查方法

啟動慢問題
開啟啟動日志
logging.level.org.springframework=DEBUG
debug=true
查看自動配置報告
分析耗時點
Bean創建慢
數據源初始化慢
組件掃描慢
使用BeanPostProcessor監控
檢查數據庫連接
優化包掃描路徑
  1. 開啟DEBUG日志
# application.properties
logging.level.org.springframework=DEBUG
debug=true
  1. 使用啟動分析工具
// 在main方法中添加
public static void main(String[] args) {System.setProperty("spring.startup.logfile", "startup.log");SpringApplication app = new SpringApplication(DemoApplication.class);app.setApplicationStartup(ApplicationStartup.buffering()); // Spring Boot 2.4+app.run(args);
}
  1. 常見的啟動慢原因
  • 包掃描范圍太大:縮小@ComponentScan的范圍
  • 數據源初始化慢:檢查數據庫連接配置
  • 不必要的自動配置:使用exclude排除不需要的配置
  • Bean初始化慢:優化Bean的初始化邏輯

Bean加載失敗的排查

當遇到Bean找不到或者依賴注入失敗時,可以這樣排查:

  1. 檢查包掃描路徑
@SpringBootApplication(scanBasePackages = {"com.example.demo", "com.example.common"})
  1. 檢查條件注解
@Component
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
public class MyService {// 如果配置不滿足,這個Bean不會被創建
}
  1. 查看Bean定義
@Component
public class BeanChecker implements ApplicationContextAware {@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {String[] beanNames = applicationContext.getBeanDefinitionNames();System.out.println("已注冊的Bean數量:" + beanNames.length);for (String beanName : beanNames) {System.out.println(beanName);}}
}

總結

好了,今天咱們把Spring Boot的啟動原理從頭到尾捋了一遍。從main方法開始,到SpringApplication的初始化,再到事件監聽、Bean加載,最后講了幾個常用的擴展點。

其實Spring Boot的啟動過程雖然復雜,但邏輯很清晰,每個階段都有明確的職責。理解了這些原理,不僅能幫我們更好地使用Spring Boot,還能在遇到問題時快速定位原因。

最后給幾個實戰建議:

  1. 調試啟動問題時,記得在SpringApplication.run()方法里打個斷點,一步步看流程
  2. 想知道哪些自動配置生效了,可以開啟debug=true,會打印自動配置報告
  3. 自定義擴展時,注意選擇合適的擴展點,別在太早的階段做太復雜的操作
  4. 生產環境中,盡量不要用反射等方式修改Spring的核心流程,容易出問題
  5. 性能優化時,可以通過BeanPostProcessor監控Bean初始化耗時,找出瓶頸

希望這篇文章能幫到大家,有什么問題歡迎在評論區交流,咱們下次再聊!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95806.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95806.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95806.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

windows環境下使用vscode以及相關插件搭建c/c++的編譯,調試環境

windows下使用vscode搭建c/c的編譯、運行、調試環境&#xff0c;需要注意的是生成的是xxx.exe可執行文件。另外使用的編譯器是mingw&#xff0c;也就是windows環境下的GNU。 我參考的網址是&#xff1a;https://zhuanlan.zhihu.com/p/1936443912806962622 文章分為2種環境搭建…

標準瓦片層級0~20,在EPSG:4326坐標系下,每個像素點代表的度數

在 EPSG:4326&#xff08;WGS84經緯度坐標系&#xff09; 下&#xff0c;瓦片層級&#xff08;Zoom Level&#xff09;的分辨率以 度/像素 為單位&#xff0c;其計算遵循 TMS Global Geodetic 規范&#xff08;單位&#xff1a;度&#xff09;。以下是 標準層級 0 至 20 的分辨…

Unity高級剔除技術全解析

目錄 ?編輯層級剔除&#xff08;Layer Culling&#xff09;原理詳解 代碼示例 業務應用場景 距離剔除&#xff08;Distance Culling&#xff09;技術細節 進階實現 開放世界優化技巧 視口裁剪&#xff08;Viewport Culling&#xff09;多攝像機協作方案 高級應用場景 …

[Linux] Linux文件系統基本管理

目錄 識別文件系統和設備 Linux 中設備 Linux 文件系統 查看設備和文件系統 lsblk命令 df命令 du命令 案例&#xff1a;查看根文件系統中哪個文件占用了最大空間 環境準備 查找過程 掛載和卸載文件系統 環境準備 掛載文件系統 卸載文件系統 卸載失敗處理 lsof …

如何在 Ubuntu 24.04 Server 或 Desktop 上安裝 XFCE

在 Ubuntu 24.04 上更改當前桌面環境或添加新的桌面環境并不是一項艱巨的任務。大多數流行的 Linux 桌面環境,包括 XFCE,都可以通過默認的 Ubuntu 24.04 LTS 系統倉庫安裝。在本教程中,我們將學習如何使用 Tasksel 工具在 Ubuntu Linux 上安裝和配置 XFCE。 訪問終端并運行…

linux下用c++11寫一個UDP回顯程序

需求&#xff1a;1&#xff09;從2個UDP端口接收數據&#xff0c;并在同樣的端口回顯。echo2&#xff09;多個處理線程&#xff0c;多個發送線程&#xff1b;3&#xff09;使用條件變量喚醒&#xff1b;#include <stack> #include <mutex> #include <atomic>…

MySQL 深分頁優化與條件分頁:把 OFFSET 換成“游標”,再用覆蓋索引抄近路

MySQL 深分頁優化與條件分頁:把 OFFSET 換成“游標”,再用覆蓋索引抄近路 這不是“玄學調優”,而是可復制的方案。本文用可復現的 DDL/造數腳本,演示為什么 OFFSET 越大越慢,如何用 條件游標(Keyset Pagination) 替換它,并配上 覆蓋索引。還會教你看 EXPLAIN/EXPLAIN A…

Unity 繩子插件 ObjRope 使用簡記

Unity 繩子插件&#xff0c;是一個基于物理的、高度逼真且可交互的繩索模擬解決方案。 其性能良好&#xff0c;能夠運行在小游戲平臺。 一、插件基本 插件資源商店地址&#xff1a; Obi Rope | Physics | Unity Asset Store 官方文檔&#xff08;手冊&#xff09;&#xff…

demo 通訊錄 + 城市選擇器 (字母索引左右聯動 ListItemGroup+AlphabetIndexer)筆記

一、城市選擇器實現筆記1. 雙層 for 循環渲染數據結構interface BKCityContent {initial: string; // 字母索引cityNameList: string[]; // 城市列表 }核心實現// 外層循環&#xff1a;字母分組 - 遍歷城市數據&#xff0c;按字母分組顯示 ForEach(this.cityContentList, (item…

【總結型】c語言中的位運算

位運算包括 & | ^ ~ << >>按位與 將某些變量中的某些位清0同時保持其他位不變。也可以用來獲取變量中的某一位。 例如&#xff1a;將int型變量n低8位全置為0&#xff0c;其余位保持不變。 n n & 0xffffff00 如何判斷一個int型變量n的第七位。 n & 0x8…

如何在FastAPI中玩轉APScheduler,實現動態定時任務的魔法?

url: /posts/4fb9e30bb20956319c783e21897a667a/ title: 如何在FastAPI中玩轉APScheduler,實現動態定時任務的魔法? date: 2025-08-16T01:14:26+08:00 lastmod: 2025-08-16T01:14:26+08:00 author: cmdragon summary: APScheduler是Python中強大的任務調度庫,支持任務持久化…

GitHub的簡單使用方法----(5)

最后一篇簡單講講git管理遠程倉庫 1.目的 備份&#xff0c;實現代碼共享集中化管理 &#xff08;將本地倉庫同步到git遠程倉庫中&#xff09; git clone 倉庫地址 以下圖為示例&#xff0c;我打開了一個別人的項目倉庫&#xff0c;點擊code能看到倉庫地址 等待完成即可 如…

C++ STL-string類底層實現

摘要&#xff1a; 本文實現了一個簡易的string類&#xff0c;主要包含以下功能&#xff1a; 1. 默認成員函數&#xff1a;構造函數&#xff08;默認/參數化&#xff09;、拷貝構造、賦值重載和析構函數&#xff0c;采用深拷貝避免內存問題&#xff1b; 2. 迭代器支持&#xff1…

【LeetCode每日一題】

每日一題3. 無重復字符的最長子串題目總體思路代碼1.兩數之和題目總體思路代碼15. 三數之和題目總體思路代碼2025.8.153. 無重復字符的最長子串 題目 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長 子串 的長度。 示例 1: 輸入: s “abcabcbb” 輸出: 3…

sharding-jdbc讀寫分離配置

一主兩從&#xff0c;爆紅是正常的&#xff0c;不知為啥 spring:shardingsphere:datasource:names: ds_master,ds_s1,ds_s2ds_master:type: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.135.100:3306/gmall_produ…

【大模型核心技術】Dify 入門教程

文章目錄一、Dify 是什么二、安裝與部署2.1 云端 SaaS 版&#xff08;快速入門&#xff09;2.2 私有化部署&#xff08;企業級方案&#xff09;三、界面導航與核心模塊3.1 控制臺概覽3.2 核心功能模塊詳解3.2.1 知識庫&#xff08;RAG 引擎&#xff09;3.2.2 工作流編排3.2.3 模…

homebrew 1

文章目錄brew(1) – macOS&#xff08;或 Linux&#xff09;上缺失的包管理器概要描述術語表基本命令install *formula*uninstall *formula*listsearch \[*text*|/*text*/]命令alias \[--edit] \[*alias*|*alias**command*]analytics \[*subcommand*]autoremove \[--dry-run]bu…

設計索引的原則有哪些?

MySQL 索引設計的核心原則是 在查詢性能與存儲成本之間取得平衡。以下是經過實踐驗證的 10 大設計原則及具體實現策略&#xff1a;一、基礎原則原則說明示例/反例1. 高頻查詢優先為 WHERE、JOIN、ORDER BY、GROUP BY 頻繁出現的列建索引? SELECT * FROM orders WHERE user_id1…

使用影刀RPA實現快遞信息抓取

最近公司項目有個需求&#xff0c;要求抓取快遞單號快遞信息&#xff0c;比如簽收地點、簽收日期等。該項目對應的快遞查詢網站是一個國外的網站&#xff0c;他們有專門的快遞平臺可以用于查詢。該平臺提供了快遞接口進行查詢&#xff0c;但需要付費。同時也提供了免費的查詢窗…

蟻劍--安裝、使用

用途限制聲明&#xff0c;本文僅用于網絡安全技術研究、教育與知識分享。文中涉及的滲透測試方法與工具&#xff0c;嚴禁用于未經授權的網絡攻擊、數據竊取或任何違法活動。任何因不當使用本文內容導致的法律后果&#xff0c;作者及發布平臺不承擔任何責任。滲透測試涉及復雜技…