bean的創建順序
在Spring Boot中,當一個配置類(使用@Configuration注解的類)中定義了多個bean時,這些bean的創建順序并不完全由它們在類中的聲明順序決定。Spring框架在創建和管理bean時,遵循了復雜的依賴注入和生命周期管理規則,這些規則決定了bean的創建和初始化順序。
以下是文心一言給出的一些影響bean創建順序的主要因素(我直接復制過來):
- 依賴關系:Spring容器會根據bean之間的依賴關系來決定創建順序。如果一個bean依賴于另一個bean,那么被依賴的bean會首先被創建。Spring通過構造函數、setter方法或字段注入等方式來識別這些依賴關系。
- @DependsOn注解:你可以使用@DependsOn注解來顯式指定一個bean依賴于其他一個或多個bean。被@DependsOn注解指定的bean會在當前bean之前被創建。
- @Order或實現Ordered接口:雖然這些主要用于排序多個相同類型的bean(例如,多個實現了同一接口的bean),但它們在某些情況下也可能間接影響bean的創建順序,尤其是當這些bean之間存在依賴關系時。
- @Bean的注冊順序:在配置類中,雖然bean的聲明順序不是決定性因素,但在沒有其他依賴關系或顯式排序的情況下,* Spring可能會按照它們在配置類中聲明的順序來創建bean。但是,這種順序并不是嚴格保證的,特別是當存在復雜的依賴關系時。
- 初始化回調:Spring提供了幾種初始化回調方法(如@PostConstruct注解的方法或實現了InitializingBean接口的afterPropertiesSet方法),這些方法在bean的所有必要屬性被容器設置之后被調用。這些回調的執行順序也受bean之間的依賴關系影響。
- 懶加載(Lazy Initialization):如果bean被標記為懶加載(通過@Lazy注解或全局配置),那么它只會在首次被請求時創建,這可能會影響bean的創建順序。
- 總結來說,Spring Boot中配置類中多個bean的創建順序主要由bean之間的依賴關系決定,而不僅僅是它們在配置類中的聲明順序。因此,在設計應用時,應該盡量避免對bean創建順序的隱式依賴,而是通過顯式的依賴關系或配置來管理bean的創建和初始化順序。
bean注解的創建
首先這個注解在方法上使用,也可以在注解使用,這里只介紹在方法上使用的情況
在方法上使用很簡單,只需要把它放在方法上就行
e.g
@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource(DruidProperties druidProperties){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}
- 這樣如果在類中當做類屬性使用,我們直接使用@Autowired注解注入就好了,
那如果是下面這樣呢,這個
@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource(DataSource dataSource){Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), dataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");return new DynamicDataSource(dataSource, targetDataSources);}
這樣根據基于springboot的自動裝配類型中的基于類型裝配,可以找到我們上面創建的那個DataSource類型的bean
spring的依賴注入
實際上在Spring框架中,當你使用@Bean注解來聲明一個bean的創建方法時,該方法中的參數并不是直接從某個地方“自動”獲取的,而是根據Spring的依賴注入(DI)機制來解決的。Spring容器在創建bean時,會分析@Bean方法中的參數,并嘗試通過以下幾種方式來解決這些參數的依賴:
- 自動裝配(Autowiring):
- 基于類型(byType):Spring會嘗試在容器中查找與參數類型相匹配的bean。如果容器中只有一個bean匹配該類型,Spring會自動注入這個bean。如果有多個bean匹配,并且沒有使用@Qualifier注解來指定具體的bean名稱,那么Spring會拋出異常,因為它不知道應該注入哪一個bean。
- 基于名稱(byName):如果你的@Bean方法參數名與容器中某個bean的名稱相匹配,并且Spring的配置中啟用了基于名稱的自動裝配(這通常是默認行為),那么Spring會嘗試注入這個bean。不過,需要注意的是,在@Bean方法中使用基于名稱的自動裝配并不是非常直觀,因為@Bean方法的參數名在編譯后可能會被優化或更改,這取決于JVM和編譯器的設置。因此,更推薦使用基于類型的自動裝配。
- 通過方法參數中的注解:
- 如果@Bean方法的參數上使用了如@Qualifier、@Value等注解,Spring會根據這些注解來解析參數的值。例如,@Qualifier注解可以用來指定應該注入哪個bean(在有多個候選bean的情況下)。@Value注解則通常用于注入配置文件中的值(如屬性文件中的值)。
- 通過構造函數或setter方法:
需要注意的是,雖然這里討論的是@Bean方法中的參數,但通常我們不會在@Bean方法內部直接創建依賴對象(即參數所代表的bean)。相反,我們會讓Spring通過構造函數或setter方法將這些依賴注入到我們的bean中。然而,對于@Bean方法本身,其參數是通過上述的依賴注入機制來解決的。 - Java配置和@Configuration類:
在@Configuration注解的類中,@Bean方法之間可以相互引用,因為Spring會確保在調用一個@Bean方法之前,它所依賴的所有bean都已經被創建和初始化。這種機制使得我們可以在@Bean方法中引用其他@Bean方法聲明的bean。
總之,@Bean方法中的參數值是通過Spring的依賴注入機制來解決的,這通常涉及到基于類型或名稱的自動裝配,以及方法參數上的注解。
bean的名稱
- 每個bean都有一個名稱,那使用@Bean注解產生的bean在容器中的bean的名稱什么,在下面有兩個DataSource類型的bean
@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource(DruidProperties druidProperties){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}/*** 這里bean沒有指定名稱那這個bean在容器中的名稱就 是方法名 "slaveDataSource"* @param druidProperties* @return*/@Bean@ConfigurationProperties("spring.datasource.druid.slave")@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")public DataSource slaveDataSource(DruidProperties druidProperties){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}
在@bean注解中有個name參數,根據描述我們可以看出name值就是這個bean的名稱,其中If left unspecified, the name of the bean is the name of the annotated method,表示如果沒有指定,那這個bean的名稱就是@Bean注解所注釋的方法的名稱,所以上面兩個bean的名稱分別是masterDataSource 和 slaveDataSource
- 如果指定了默認名稱,那么這個bean在容器里就叫dynamicDataSource
@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");return new DynamicDataSource(masterDataSource, targetDataSources);}
那么參數中 public DynamicDataSource dataSource(DataSource masterDataSource)中的形參masterDataSource來自哪里呢,實際上它是spring從容器中找一個類型為DataSource,名為 masterDataSource的bean,如果把這里改成下面這樣就會報錯,因為容器中現在有兩個DataSource類型的bean,masterDataSource 和 slaveDataSource,這里形參名叫做dataSource,spring根據名稱找不到,根據類型能找到兩個,不知道注入哪一個,就會報錯
@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource1(DataSource dataSource){Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), dataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");return new DynamicDataSource(dataSource, targetDataSources);}
這個時候@Qualifier注解就可以使用了,可以用@Qualifier注解指定bean,將masterDataSource的值 賦值到形參 dataSource
/**** 這里的形參 dataSource指的容器中DataSource類型的 名為dataSource 的 bean* 但是這里面容器里面沒有這個bean,就可以用@Qualifier注解指定bean,將masterDataSource的值 賦值到形參 dataSource*/@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource dataSource1(@Qualifier("masterDataSource") DataSource dataSource){Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), dataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");return new DynamicDataSource(dataSource, targetDataSources);}
這里實際上還有個@Primary注解,假如有多個相同類型的bean,可以使用@Primary來標明優先用那個bean,但是同一種類型的bean,只能有一個使用@Primary注解,實際上DynamicDataSource是DataSource的子類,所以實際上它們是同一種bean,所以只能有一個@Primary注解