Spring Bean生命周期的完全指南

簡介:超越@Bean——揭開Spring Bean的隱秘生活

想象一場復雜宏大的舞臺劇。作為觀眾,我們看到的是最終的演出——一個流暢運行的應用程序。但在這光鮮的幕后,隱藏著一套嚴謹細致的流程:選角(實例化Bean)、試裝(注入依賴)、彩排(初始化),直到演員(Bean)準備好登臺亮相。本教程將拉開這塊神秘的帷幕,帶您一窺Spring世界后臺的精妙運作。

許多Spring開發者能夠高效地使用框架,但往往將Spring容器視為一個“黑匣子”。這種認知局限了他們解決復雜問題的能力,例如資源管理、循環依賴和高級配置等。真正理解Bean的生命周期,能讓開發者從一個框架的使用者,蛻變為一名真正的架構師 1。它賦予您駕馭框架底層機制、編寫更高效、更健壯代碼的信心。

本指南將系統性地引領您走過這段旅程。我們將從“為什么需要IoC”這個根本問題出發,逐步深入到一個單例(Singleton)Bean從誕生到消亡的完整時間線,探索像BeanPostProcessor這樣強大的擴展點,并最終分析不同的作用域(Scope)如何戲劇性地改變一個Bean的生命軌跡。

第一章:舞臺與演員——理解Spring IoC容器

在深入探討Bean的生命周期之前,我們必須先了解它所處的環境——Spring IoC容器,以及它所遵循的核心哲學——控制反轉(Inversion of Control)。

控制反轉(IoC)的哲學

在傳統的編程模式中,對象通常需要自己創建或查找其依賴的對象。例如,一個Car對象可能會在自己的構造函數中通過new Engine()來創建它所依賴的Engine對象。這種方式導致了對象之間的高度耦合,使得代碼難以測試和維護。

控制反轉(IoC)則徹底顛覆了這一模式。它的核心思想是:將對象創建和管理的控制權從應用程序代碼中轉移到一個外部容器中 4。繼續以汽車為例,

Car對象不再自己制造引擎,而是向一個“工廠”(即IoC容器)聲明:“我需要一個引擎”。工廠則負責制造引擎,并將其“注入”到汽車中。這種模式從根本上降低了組件間的耦合度,提升了系統的模塊化和靈活性 7。

**依賴注入(Dependency Injection, DI)**是實現控制反轉最主要、最具體的技術手段 8。容器通過DI,在運行時動態地將一個對象所依賴的其他對象注入進去,從而完成了對象間的“裝配”。

IoC容器:您應用程序的龍骨

Spring IoC容器是Spring框架的核心,它是一個負責實例化、配置、組裝和管理對象(即Bean)整個生命周期的程序 7。它通過讀取配置元數據來獲取關于Bean的指令,這些元數據可以通過XML文件、Java注解或基于Java的配置類來提供 5。

Spring提供了兩種主要的容器類型:

  1. BeanFactory:這是最基礎的IoC容器,提供了依賴注入和Bean生命周期管理的基本支持。它通常采用“懶加載”(lazy-loading)策略,即只有在首次請求Bean時才會進行實例化。這使得它在資源受限的環境(如移動應用)中非常有用 4。

  2. ApplicationContext:這是BeanFactory的超集,也是絕大多數Spring應用的首選容器。它建立在BeanFactory之上,并增加了更多企業級功能,如事件發布、國際化(i18n)支持、與Spring AOP的集成等 4。

    ApplicationContext通常在容器啟動時就預先實例化所有單例Bean,從而可以及早發現配置錯誤。

在本教程的后續部分,當我們提及“容器”時,通常指的是功能更強大的ApplicationContext

定義演員:什么才是一個“Spring Bean”?

一個常見的誤解是,Spring Bean是一種特殊的Java類。實際上,任何一個由Spring IoC容器實例化、組裝和管理的對象,都可以被稱為Spring Bean 6。它本質上就是一個普通的Java對象(POJO)。

容器通過讀取**配置元數據(Configuration Metadata)**來了解應該創建哪些對象、如何創建以及它們之間的依賴關系。這份元數據就像是創建Bean的“藍圖”或“配方” 7。現代Spring應用主要通過以下三種方式提供元數據:

  • 基于注解的配置:在類上使用@Component@Service@Repository@Controller等注解,讓Spring自動掃描并注冊它們。

  • 基于Java的配置:在一個帶有@Configuration注解的類中,使用@Bean注解的方法來聲明Bean。

  • 基于XML的配置:在XML文件中使用<bean>標簽來定義Bean及其依賴。

一旦一個對象被納入容器的管理范疇,它的生命周期就完全由容器來掌控,從出生到死亡,每一步都遵循著一套嚴謹而有序的規則。

第二章:一個單例Bean的完整生命周期——精密的時間線

Spring中最常見、也是默認的作用域是單例(Singleton)。對于單例Bean,容器會確保在整個應用程序生命周期中只創建一個實例。理解這個實例從誕生到消亡的完整過程,是掌握Spring核心機制的關鍵。

鳥瞰圖:從誕生到消亡

一個單例Bean的生命周期可以概括為以下幾個核心階段,這為我們接下來的深入探索提供了一張路線圖 12:

  1. 實例化(Instantiation):創建Bean的實例。

  2. 屬性填充(Population):注入Bean的依賴。

  3. 初始化(Initialization):執行一系列回調,使Bean達到可用狀態。

  4. 服務中(In Service):Bean處于活動狀態,處理應用程序請求。

  5. 銷毀(Destruction):在容器關閉時,執行清理工作。

!(https://i.imgur.com/kY3f5vD.png)

階段一:實例化——創造的火花

生命周期的第一步是實例化。當容器啟動并讀取配置元數據后,它會為每個Bean定義找到對應的Java類。然后,容器通過Java反射機制調用該類的構造函數(或指定的工廠方法)來創建一個對象實例 2。

此時,對象已經在內存中被創建,但它還只是一個“毛坯房”——它的屬性都是默認值(如null或基本類型的零值),尚未被賦予任何有意義的數據或依賴。

階段二:屬性填充——編織依賴之網

實例化之后,容器進入屬性填充階段。在這個階段,容器會檢查Bean定義中的依賴關系,并將這些依賴注入到剛剛創建的實例中 1。這通常是通過以下方式實現的:

  • 構造器注入:在實例化階段就已經完成。

  • Setter注入:容器調用Bean的setter方法。

  • 字段注入:容器通過反射直接設置帶有@Autowired等注解的字段。

完成屬性填充后,Bean實例不再是一個孤立的對象,它已經與它的協作者(其他Bean)建立了聯系。然而,它可能還需要進行一些內部狀態的設置才能真正投入使用。

階段三:初始化——宏大的裝配流水線

初始化是整個生命周期中最復雜、功能最豐富的階段。它為開發者提供了多個切入點,用以執行自定義的初始化邏輯。這些切入點的執行順序是確定且至關重要的,構成了Spring強大的擴展能力的基礎。

步驟 3.1:獲得自我感知(Aware接口)

在執行任何自定義初始化邏輯之前,Spring會檢查Bean是否實現了特定的Aware(感知)接口。如果實現了,Spring會調用相應的方法,將容器自身的基礎設施資源注入給Bean。這使得Bean能夠“感知”到它在容器中的存在和環境 16。

常見的Aware接口包括:

  • BeanNameAware:如果實現此接口,容器會調用其setBeanName(String name)方法,將Bean在配置文件中定義的ID(或名稱)傳遞給它。

  • BeanFactoryAware:如果實現此接口,容器會調用其setBeanFactory(BeanFactory beanFactory)方法,將創建該Bean的BeanFactory實例傳遞給它。

  • ApplicationContextAware:如果實現此接口,容器會調用其setApplicationContext(ApplicationContext applicationContext)方法,將Bean所在的ApplicationContext實例傳遞給它。

這些接口的調用發生在依賴注入之后、自定義初始化方法(如@PostConstruct)之前。這種設計是有意為之的。它確保了當Bean開始執行自己的初始化邏輯時,它已經具備了訪問容器基礎設施的能力。這主要用于框架級別的集成,例如,一個Bean可能需要通過ApplicationContext以編程方式獲取其他Bean。然而,在業務代碼中應謹慎使用Aware接口,因為這會將代碼與Spring框架緊密耦合 1。

步驟 3.2:第一次攔截:BeanPostProcessor.postProcessBeforeInitialization

接下來,容器會將Bean實例傳遞給所有已注冊的BeanPostProcessor實現的postProcessBeforeInitialization方法。這是在調用Bean自身的初始化方法(如@PostConstruct之前的第一個重要擴展點 1。

BeanPostProcessor(Bean后置處理器)是一種特殊的Bean,它不處理自己的業務邏輯,而是用于對容器中的其他Bean進行加工處理。在此階段,后置處理器可以修改Bean實例,甚至可以返回一個完全不同的對象(例如一個代理對象)來替換原始實例。如果它返回了新的對象,那么后續的所有操作都將作用于這個新對象上。

步驟 3.3:核心初始化回調(對比分析)

現在,輪到Bean執行自己的初始化邏輯了。Spring提供了三種主要的方式來定義初始化回調方法。如果一個Bean同時定義了多種方式,它們的執行順序是固定的 1:

  1. 使用@PostConstruct注解的方法:這是JSR-250規范定義的一部分,被認為是現代Spring應用的最佳實踐 20。它將初始化邏輯與Bean的源代碼放在一起,清晰且解耦。

  2. InitializingBean接口的afterPropertiesSet()方法:如果Bean實現了InitializingBean接口,容器會調用其afterPropertiesSet()方法。這種方式的缺點是它讓業務代碼與Spring框架的API產生了緊密耦合。

  3. 自定義的init-method:可以在XML配置的<bean>標簽中使用init-method屬性,或在Java配置的@Bean注解中使用initMethod屬性來指定一個自定義的初始化方法。這種方式將代碼與框架解耦,但配置與代碼是分離的。

這三種方式反映了Spring框架設計理念的演進。從早期的InitializingBean接口(緊密耦合),到XML的init-method(配置繁瑣),再到現代的@PostConstruct注解(約定優于配置,代碼內聚),趨勢是朝著更簡潔、更解耦、可讀性更好的方向發展。

步驟 3.4:最后的打磨:BeanPostProcessor.postProcessAfterInitialization

當Bean完成了自身的初始化回調后,容器會再次將其傳遞給所有BeanPostProcessorpostProcessAfterInitialization方法 1。

這是生命周期中另一個至關重要的擴展點。許多Spring的核心功能,如AOP代理的創建,正是在這一步完成的。后置處理器在此處接收到一個完全初始化好的Bean實例,然后可以對其進行最終的包裝或修改,例如創建一個代理來包裹原始Bean,以添加事務管理或安全等橫切關注點。最終從這個方法返回的對象,才是真正被應用程序使用的Bean實例。

特性@PostConstructInitializingBeaninit-method
類型注解 (JSR-250)接口 (Spring)配置 (XML/@Bean)
耦合度與Spring解耦與Spring緊密耦合與Spring解耦
配置位置類內部,聲明式類內部,編程式外部XML或配置類
執行順序第一第二第三
最佳實踐推薦不推薦可行,但注解更優

階段四:服務中——Bean的辛勤工作

經過初始化的所有步驟后,Bean終于進入了“完全可用”的狀態。對于單例Bean,容器會將這個最終的(可能是被代理的)實例存儲在一個內部緩存中 1。從此刻起,每當有其他Bean需要依賴它,或者應用程序通過

ApplicationContext.getBean()請求它時,容器都會返回這個緩存中的唯一實例。Bean開始履行它的職責,處理業務邏輯,直到容器關閉。

階段五:銷毀——優雅地退場

ApplicationContext被關閉時(例如,應用程序正常停止),容器會觸發其管理的單例Bean的銷毀流程。這個階段為Bean提供了一個釋放資源的機會,如關閉數據庫連接、文件句柄或網絡套接字 1。

與初始化類似,銷毀回調也有多種定義方式,并且遵循固定的執行順序:

  1. 使用@PreDestroy注解的方法:同樣是JSR-250規范的一部分,是銷毀回調的首選方式。

  2. DisposableBean接口的destroy()方法:如果Bean實現了DisposableBean接口,容器會調用其destroy()方法。

  3. 自定義的destroy-method:在XML或@Bean注解中指定的自定義銷毀方法。

值得注意的是,在非Web應用程序中,為了確保銷毀回調能夠被觸發,你需要手動向JVM注冊一個關閉鉤子(Shutdown Hook),例如調用AbstractApplicationContextregisterShutdownHook()方法。在Web應用中,這個過程通常由Web容器自動管理 18。

第三章:終極擴展點——深入BeanPostProcessor

在Bean的生命周期中,我們反復提到了BeanPostProcessor。它不僅僅是一個簡單的回調接口,更是Spring框架最具威力的擴展機制之一,是理解Spring AOP、事務管理等高級功能如何實現的關鍵。

BeanPostProcessor是什么?終極工廠鉤子

BeanPostProcessor是一個特殊的Bean,它在Spring容器級別工作,其作用是在Bean初始化階段的前后,對容器中的其他Bean實例進行干預和處理 17。它就像是Bean工廠流水線上的一個“質檢/加工站”,每個Bean在完成基礎裝配后,都必須經過這里進行檢查和可能的再加工。

它定義了兩個核心方法,分別對應初始化前后的兩個關鍵時刻 1:

  • postProcessBeforeInitialization(Object bean, String beanName):在Bean的初始化回調(如@PostConstruct之前被調用。

  • postProcessAfterInitialization(Object bean, String beanName):在Bean的初始化回調之后被調用。

這兩個方法都可以返回原始的Bean,或者返回一個包裝(代理)過的Bean。如果返回了新的對象,那么這個新對象將取代原始對象,成為容器中最終的Bean實例。

實踐中的魔法:Spring AOP與代理的誕生

BeanPostProcessor的強大之處在于,它為Spring實現聲明式、非侵入性的功能增強提供了底層機制。Spring AOP(面向切面編程)就是其最經典的應用。

讓我們來追蹤一個帶有@Transactional注解的@Service Bean是如何被賦予事務能力的,這個過程清晰地展示了生命周期與AOP的聯動 22:

  1. Bean的常規生命周期:容器實例化MyServiceImpl,并填充其依賴。

  2. 初始化回調:容器調用MyServiceImpl上的@PostConstruct等初始化方法。此時,它還是一個普通的Java對象。

  3. 后置處理:在postProcessAfterInitialization階段,一個Spring內部的BeanPostProcessor(名為AnnotationAwareAspectJAutoProxyCreator)開始工作 24。

  4. 檢查與決策:這個后置處理器會檢查MyServiceImpl類及其方法上是否存在AOP相關的注解,比如@Transactional

  5. 代理創建:當它檢測到@Transactional注解時,它判斷這個Bean需要被代理。于是,它使用動態代理技術(JDK動態代理或CGLIB)創建一個代理對象。這個代理對象包裹了原始的MyServiceImpl實例 22。代理對象內部包含了開啟事務、提交或回滾事務的邏輯。

  6. 偷梁換柱AnnotationAwareAspectJAutoProxyCreatorpostProcessAfterInitialization方法最終返回這個代理對象,而不是原始的MyServiceImpl實例。

  7. 注入代理:容器接收到這個代理對象,并將其存入單例緩存。之后,任何其他需要注入MyService的地方,得到的都將是這個具備事務能力的代理對象,而不是原始對象。

這個過程完美地詮釋了Spring如何通過生命周期鉤子來實現強大的功能抽象。開發者只需簡單地添加一個@Transactional注解,無需編寫任何事務管理模板代碼。底層的Bean生命周期機制,特別是BeanPostProcessor,自動、透明地完成了從一個普通Bean到一個事務性組件的“升級”。這種非侵入式的能力正是Spring框架價值的核心所在。

實現一個自定義BeanPostProcessor

為了更具體地理解其工作原理,我們可以實現一個簡單的自定義BeanPostProcessor。例如,下面的處理器會在每個Bean初始化后,打印出其類名:

Java

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 在初始化前不做任何操作,直接返回原始beanreturn bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("Bean '" + beanName + "' of type [" + bean.getClass().getName() + "] has been initialized.");// 在初始化后,返回原始beanreturn bean;}
}

只需將這個類定義為一個Bean(通過@Component),Spring容器就會自動檢測到它,并用它來處理之后創建的所有其他Bean。

第四章:兩種作用域的故事——Singleton與Prototype的生命周期

Bean的作用域(Scope)定義了Bean實例的生命周期和可見性范圍。雖然Spring支持多種作用域(如requestsession等),但singleton(單例)和prototype(原型)之間的巨大差異最能體現作用域對生命周期的深刻影響 8。

Singleton生命周期:由容器全權管理

正如第二章所詳述,單例Bean的生命由容器全權負責。容器創建它、初始化它、管理它,并在最后銷毀它。在整個應用生命周期中,容器始終持有一個對該單例實例的強引用,確保其唯一性 29。

Prototype生命周期:容器的“閱后即焚”策略

原型作用域的行為則截然不同。當一個prototype作用域的Bean被請求時(無論是通過依賴注入還是getBean()調用),容器每次都會創建一個全新的實例 30。

容器會像對待單例Bean一樣,對這個新實例進行實例化、屬性填充和初始化@PostConstruct等初始化回調依然會被調用)。然而,一旦這個初始化完成的Bean實例被交給請求方,容器的職責就此終結。容器不會保留對該原型實例的任何引用,也不會再對其進行任何管理 30。

關鍵差異:為什么@PreDestroy對Prototype無效?

這是初學者最容易困惑的地方。為什么原型Bean的銷毀方法(如@PreDestroy注解的方法)不會被容器調用?18

答案在于責任的轉移和對內存泄漏的規避

試想一下,如果容器需要負責銷毀它創建的每一個原型Bean,它就必須保存所有這些實例的引用。原型Bean可能被頻繁地、大量地創建。如果容器一直持有這些引用,那么即使這些Bean在業務邏輯中已經不再被使用,它們也永遠無法被Java的垃圾回收器(Garbage Collector)回收,這將不可避免地導致嚴重的內存泄漏 37。

因此,Spring做出了一個明確的設計決策:對于原型Bean,容器的角色僅限于一個“工廠”。它負責生產出合格的產品(初始化完成的Bean),然后將產品(以及后續的維護和報廢責任)完全移交給客戶(即請求該Bean的代碼)。清理原型Bean所持有的資源(如數據庫連接)的責任,落在了使用者的肩上 33。

Singleton-Prototype困境:解決作用域依賴難題

當一個生命周期長的Bean(如Singleton)依賴一個生命周期短的Bean(如Prototype)時,會出現一個棘手的問題。

假設一個單例SingletonService依賴一個原型PrototypeComponent

Java

@Service // 默認是singleton
public class SingletonService {@Autowiredprivate PrototypeComponent prototypeComponent;//...
}

由于依賴注入只在SingletonService創建時發生一次,所以prototypeComponent也只會被創建和注入一次。之后,SingletonService將永遠持有這同一個PrototypeComponent的實例,完全違背了prototype作用域的初衷 35。

為了解決這個問題,Spring提供了幾種方案:

  1. 作用域代理(Scoped Proxies):這是最常用且優雅的解決方案。通過在原型Bean上添加proxyMode屬性,指示Spring注入一個代理對象。

    Java

    @Component
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class PrototypeComponent {... }
    

    現在,注入到SingletonService中的不再是PrototypeComponent的真實實例,而是一個代理。每當SingletonService調用這個代理的方法時,代理都會向容器請求一個新的PrototypeComponent實例,并將調用委托給這個新實例 36。

  2. 方法注入(@Lookup:通過@Lookup注解,你可以告訴Spring覆蓋單例Bean中的某個方法,使其每次調用都返回一個新的原型Bean實例。

    Java

    @Service
    public abstract class SingletonService {@Lookupprotected abstract PrototypeComponent createPrototypeComponent();public void doWork() {PrototypeComponent component = createPrototypeComponent(); // 每次調用都會得到新實例//...}
    }
    
  3. 注入ApplicationContext:直接在單例Bean中注入ApplicationContext,然后每次需要時手動調用context.getBean("prototypeComponent")。這種方法雖然可行,但因為它讓業務代碼與容器API耦合,通常不被推薦。

特性Singleton Scope (默認)Prototype Scope
實例創建每個容器僅一個實例。每次請求 (getBean()) 都創建新實例。
狀態管理適用于無狀態Bean。適用于有狀態Bean。
初始化回調每個容器生命周期中調用一次。每個新實例都會調用。
銷毀回調容器關閉時會調用容器不會調用
責任容器管理完整生命周期。客戶端代碼在使用后負責清理。
性能高(對象復用)。較低(有對象創建開銷)。

結論:從開發者到架構師——掌控Bean的生命周期

通過本次深入的探索,我們揭開了Spring Bean生命周期的神秘面紗。我們看到,它并非一個黑盒,而是一個設計精良、邏輯嚴謹且高度可擴展的流程。

核心要點回顧:

  • 生命周期是一個可預測的有序過程:從實例化到銷毀,每個階段都有其明確的職責和順序,這為Spring的穩定運行提供了保障。

  • BeanPostProcessor是終極擴展鑰匙:它是Spring實現AOP、事務管理等高級功能的基石,理解它就等于掌握了擴展和定制Spring核心行為的能力。

  • 作用域決定了Bean與容器的關系:一個Bean的作用域從根本上改變了它的生命軌跡,特別是誰來負責它的最終清理工作。

掌握這些知識,不僅僅是學術上的滿足。它能直接轉化為解決實際問題的能力:高效地管理資源(例如,知道何時以及如何清理原型Bean),調試復雜的應用行為(例如,理解為什么一個事務方法在內部調用時會失效),以及最終編寫出更健壯、高效和易于維護的企業級應用程序 1。

現在,當您再次審視Spring容器時,希望您看到的不再是一個神秘的魔法盒,而是一個強大、透明、且盡在掌控的精密系統。您已經具備了從一名框架使用者,向一名能夠駕馭其核心機制的架構師邁進所需的關鍵知識。

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

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

相關文章

網絡安全A模塊專項練習任務九解析

任務九&#xff1a;Linux操作系統安全配置-2任務環境說明&#xff1a; (Linux)系統&#xff1a;用戶名root&#xff0c;密碼1234561. 設置禁止使用最近用過的6個舊密碼&#xff0c;將配置文件中對應的部分截圖&#xff1b;編輯/etc/pam.d/system-auth文件&#xff0c;找到passw…

Linex進程管理

一、進程查看命令1.pstree用于查看進程樹之間的關系&#xff0c;誰是父進程&#xff0c;誰是子進程&#xff0c;可以清楚的看出來是誰創建了誰語法&#xff1a;pstree [選項] -A各進程樹之間的連接以ASCII碼字符來連接-U各進程樹之間的連接以utf8字符來連接&#xff0c;某些終…

手寫MyBatis第47彈:Interceptor接口設計與Invocation上下文傳遞機制--MyBatis動態代理生成與方法攔截的精妙實現

&#x1f942;(???)您的點贊&#x1f44d;?評論&#x1f4dd;?收藏?是作者創作的最大動力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;點贊&#x1f44d;收藏??留言&#x1f4dd;歡迎留言討論 &#x1f525;&#x1f525;&…

自動駕駛中的傳感器技術37——Lidar(12)

這里對當前Lidar中的一些常見問題進行專項論述。首先以禾賽Lidar為例&#xff0c;列出相關參數&#xff0c;以備論述。 圖1 禾賽AT128參數圖2 禾賽AT360參數圖3 禾賽AT1440參數圖4 禾賽AT128可靠性驗證項圖5 禾賽AT128產品證書1、Lidar的線束是什么&#xff0c;由什么決定&…

Meteor主題友鏈頁面自研

發布于&#xff1a;Eucalyptus-Blog Meteor主題雖然設計簡約現代&#xff0c;但由于缺乏原生的友情鏈接管理功能&#xff0c;許多博主只能將友情鏈接勉強添加在網站底部&#xff0c;這不僅影響頁面美觀&#xff0c;也不便于訪客查找和互動&#xff1b;為了解決這一痛點&#xf…

QT控件QPlainTextEdit、QTextEdit與QTextBrowser的區別

一.主要功能對比二.關鍵功能差異1.文本類型支持QPlainTextEdit&#xff1a;僅支持純文本&#xff08;Plain Text&#xff09;&#xff0c;不處理任何格式&#xff08;如字體、顏色、鏈接、圖片等&#xff09;。文本以原始字符形式存儲&#xff0c;適合處理日志、代碼、配置文件…

【思考】WSL是什么

WSL WSL是什么呢&#xff1f; WSL 是 windows subsystem for linux 的簡寫&#xff0c;指的是 windows10 的一個子系統&#xff0c;這個子系統的作用是在 windows 下運行 linux 操作系統。 有了WSL&#xff0c;就可以在 windows10 中運行linux操作系統了。許多在 linux 種運行的…

基于單片機智能飲水機/智能熱水壺

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽 概述 基于單片機的智能飲水機系統通過嵌入式技術實現水溫控制、水量監測及用戶交互功能。系統采用STM3…

Unity游戲打包——iOS打包基礎、傳包

本文由 NRatel 歷史筆記整理而來&#xff0c;如有錯誤歡迎指正。 相關參考文檔 Unity文檔 -> 平臺開發 -> IOS https://docs.unity3d.com/cn/2021.3/Manual/iphone.html Unity導出的Xcode 項目的結構 Modifying an Xcode project use Xcode.PBXProject. https://doc…

pyside6小項目:進制轉換器

from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication,QWidgetclass MyWindow(QWidget):def __init__(self):super().__init__()self.ui QUiLoader().load(trans.ui)self.ui.show()#stor data type dictionaryself.lengthVar {米:100, 千米:…

再見 K8s!3款開源的云原生部署工具

前文&#xff0c;和大家分享了云原生中的核心工具 K8s&#xff1a; 關于 K8s&#xff1a;入門&#xff0c;這篇就夠了 K8s是個好東西&#xff0c;就是上手門檻有點高。這不&#xff0c;需求就來了&#xff1f; 有需求&#xff0c;就有工具。 為了解決K8s的配置難題&#xf…

C++ 快速復習指南(上半部分)

1.基礎語法基本結構#include <iostream> 頭名 using namesapce std ; 統一使用命名空間 int main () { 程序執行門戶 主題內容}基本輸出 cout << "string " << endl; // 輸出 string 變量和數據類型 格式int intger 10 ;常量的引入 需要在變量…

ArcGIS Pro 地圖打包與解包

如果需要在ArcGIS Pro 打包某一個地圖文檔&#xff0c;在 菜單欄中 點擊 共享&#xff0c;點擊地圖。彈出 打包地圖 面板&#xff0c;可以打包到Online、打包到地圖包&#xff0c;選擇將包保存到文件&#xff0c;修改項目詳細信息&#xff0c;點擊 包&#xff0c;即可實現打包。…

sunset: twilight靶場

sunset: twilight 來自 <sunset: twilight ~ VulnHub> 1&#xff0c;將兩臺虛擬機網絡連接都改為NAT模式 2&#xff0c;攻擊機上做namp局域網掃描發現靶機 nmap -sn 192.168.23.0/24 那么攻擊機IP為192.168.23.128&#xff0c;靶場IP192.168.23.145 3&#xff0c;對靶機…

【機器學習基礎】無監督學習算法的現代演進:從數據探索到智能系統的自主發現能力

1. 引言:無監督學習在人工智能革命中的核心價值 在人工智能技術飛速發展的今天,無監督學習正在成為推動AI系統實現真正智能的關鍵技術。與需要大量標注數據的監督學習不同,無監督學習能夠從原始數據中自主發現隱藏的模式和結構,這種能力使其在現代AI應用中具有不可替代的價…

PetaLinux的JTAG啟動

csdn–PetaLinux 使用技巧與緩存配置 xilinx官網–PetaLinux 工具文檔參考指南 (ug1144) xilinx官網–設備樹配置文檔 內核官網–設備樹文檔 軟硬件準備 分類項目說明/用途驗證方法示例硬件JTAG 線JTAG 下載、調試—UART 串口線查看 zynq 啟動日志—網口線用于 TFTP 下載—…

單片機中的按鍵防抖

按鈕&#xff08;按鍵&#xff09;抖動是單片機開發中常見的硬件問題&#xff0c;本質是機械觸點接觸瞬間的物理彈跳導致的電信號不穩定。消除抖動&#xff08;防抖&#xff09;是確保按鍵狀態檢測準確的關鍵&#xff0c;下面從原理到實現詳細講解。 一、按鈕抖動的原理&#x…

面經分享--小米Java一面

目錄 1.Kafka和RocketMQ的區別 2.反射的作用 3.類加載的具體過程&#xff0c;雙親委派模型的機制 4.TCP的四次揮手 5.多線程的優勢 6.死鎖產生的原因&#xff0c;怎么解決 7.Java并發的工作原理 8.常用的git命令 9.算法題 1.leetcode 3.無重復字符的最長子串&#xff…

Python在邊緣計算與物聯網中的創新實踐:實時數據處理與設備控制

近年來&#xff0c;Python語言的普及度持續攀升&#xff0c;尤其在人工智能、數據科學等熱門領域備受青睞。然而&#xff0c;一個新興趨勢——邊緣計算與物聯網&#xff08;IoT&#xff09;的結合——正悄然改變技術格局。邊緣計算強調在數據源頭進行實時處理&#xff0c;減少云…

Spring Cloud Gateway 網關(五)

目錄 一 概念引入 二 具體使用 1 首先創建一個網關模塊 2 啟動類 3 配置類 4 對應方法的修改 5 展示借助81端口進行轉發控制 6 斷言規則?編輯 三 過濾器 1 將前置的請求參數給過濾掉&#xff0c;降低繁瑣程度。 2 默認過濾器 3 全局過濾器 4 自定義過濾器工廠 5…