Sprin中Bean的順序使用及說(shuō)明
一、Bean的加載順序
spring容器載入bean順序是不確定的,在一定的范圍內(nèi)bean的加載順序可以控制。
spring容器載入bean雖然順序不確定,但遵循一定的規(guī)則:
1、按照字母順序加載(同一文件夾下按照字母數(shù)序;不同文件夾下,先按照文件夾命名的字母順序加載)
2、不同的bean聲明方式不同的加載時(shí)機(jī),順序總結(jié):@ComponentScan > @Import > @Bean
這里的ComponentScan指@ComponentScan及其子注解,Bean指的是@configuration + @bean
同時(shí)需要注意的是:
- (1)Component及其子注解申明的bean是按照字母順序加載的
- (2)@configuration + @bean是按照定義的順序依次加載的
- (3)@import的順序,就是bean的加載順序
- (4)在xml中,通過(guò)<bean id="">方式聲明的bean也是按照代碼的編寫(xiě)順序依次加載的
- (5)同一類(lèi)中加載順序:Constructor >> @Autowired >> @PostConstruct >> @Bean
- (6)同一類(lèi)中加載順序:靜態(tài)變量 / 靜態(tài)代碼塊 >> 構(gòu)造代碼塊 >> 構(gòu)造方法(需要特別注意的是靜態(tài)代碼塊的執(zhí)行并不是優(yōu)先所有的bean加載,只是在同一個(gè)類(lèi)中,靜態(tài)代碼塊優(yōu)先加載)
特別情況下,如果想手動(dòng)控制部分bean的加載順序,有如下方法:
方法1:構(gòu)造方法依賴(lài) (推薦)
@Component public class CDemo1 { private String name = "cdemo 1"; public CDemo1(CDemo2 cDemo2) { System.out.println(name); } }
@Component public class CDemo2 { private String name = "cdemo 2"; public CDemo2() { System.out.println(name); } }
CDemo2在CDemo1之前被初始化。
限制
要有注入關(guān)系,如:CDemo2通過(guò)構(gòu)造方法注入到CDemo1中,若需要指定兩個(gè)沒(méi)有注入關(guān)系的bean之間優(yōu)先級(jí),則不太合適(比如我希望某個(gè)bean在所有其他的Bean初始化之前執(zhí)行)
循環(huán)依賴(lài)問(wèn)題,如過(guò)上面的CDemo2的構(gòu)造方法有一個(gè)CDemo1參數(shù),那么循環(huán)依賴(lài)產(chǎn)生,應(yīng)用無(wú)法啟動(dòng)
另外一個(gè)需要注意的點(diǎn)是,在構(gòu)造方法中,不應(yīng)有復(fù)雜耗時(shí)的邏輯,會(huì)拖慢應(yīng)用的啟動(dòng)時(shí)間
方法2:參數(shù)注入
在@Bean標(biāo)注的方法上,如果你傳入了參數(shù),springboot會(huì)自動(dòng)會(huì)為這個(gè)參數(shù)在spring上下文里尋找這個(gè)類(lèi)型的引用。并先初始化這個(gè)類(lèi)的實(shí)例。
利用此特性,我們也可以控制bean的加載順序。
以上結(jié)果,beanB先于beanA被初始化加載。
需要注意的是,springboot會(huì)按類(lèi)型去尋找。如果這個(gè)類(lèi)型有多個(gè)實(shí)例被注冊(cè)到spring上下文,那你就需要加上@Qualifier(“Bean的名稱(chēng)”)來(lái)指定
方法3:@Autowired 注入到所需的服務(wù)中
跟在xml配置中寫(xiě) ref差不多的功能 spring 會(huì)解析到這個(gè)會(huì)依賴(lài)springBeanManager 所以會(huì)先加載springBeanManager
@Component public class SystemInit { @Autowired private SpringBeanManager springBeanManager; @PostConstruct public void init() { //初始化 script job bean GroovyBeanInit.InitScriptJob(); } }
方法4:@DependsOn(“xxx”)
沒(méi)有直接的依賴(lài)關(guān)系的,可以通過(guò)@DependsOn注解,我們可以在bean A上使用@DependsOn注解 ,告訴容器bean B應(yīng)該優(yōu)先被加載初始化。
不推薦的原因:這種方法是通過(guò)bean的名字(字符串)來(lái)控制順序的,如果改了bean的類(lèi)名,很可能就會(huì)忘記來(lái)改所有用到它的注解,那就問(wèn)題大了。
當(dāng)一個(gè)bean需要在另一個(gè)bean實(shí)例化之后再實(shí)例化時(shí),可使用這個(gè)注解。
@Component("dependson02") public class Dependson02 { Dependson02(){ System.out.println(" dependson02 Success "); } }
@Component @DependsOn("dependson02") public class Dependson01 { Dependson01(){ System.out.println("Dependson01 success"); } }
執(zhí)行結(jié)果:
結(jié)果:
dependson02 Success
Dependson01 success
方法5:BeanDefinitionRegistryPostProcessor接口
通過(guò)實(shí)現(xiàn)BeanDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry方法中通過(guò)BeanDefinitionRegistry獲取到所有bean的注冊(cè)信息,將bean保存到LinkedHashMap中,并從BeanDefinitionRegistry中刪除,然后將保存的bean定義排序后,重新再注冊(cè)到BeanDefinitionRegistry中,即可實(shí)現(xiàn)bean加載順序的控制。
/** *配置類(lèi) */ @Configuration @ComponentScan(basePackages = "demo") public class AppConfig extends WebMvcConfigurationSupport { /** * 注冊(cè)用于控制bean加載順序的處理器 * @return */ @Bean public static DemoProcessor demoProcessor() { return new DemoProcessor(); } } /** * */ public class DemoProcessor implements BeanDefinitionRegistryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { String[] beanDefinitionNames = registry.getBeanDefinitionNames(); int index = 0; //保留前n個(gè)關(guān)鍵bean的順序 for(;index<beanDefinitionNames.length;index++) { if(AppConfig.class.getName().equals(registry.getBeanDefinition(beanDefinitionNames[index]).getBeanClassName())) { break; } } Map<String, BeanDefinition> beans = new LinkedHashMap<>(beanDefinitionNames.length-index); for(;index<beanDefinitionNames.length;index++) { BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionNames[index]); beans.put(beanDefinitionNames[index], beanDefinition); registry.removeBeanDefinition(beanDefinitionNames[index]); } //TODO ...排序邏輯,注意beans中可能還包含有其他spring自身定義的bean List<String> orderdBeanNames = new ArrayList<>(); //將排好序的bean再次注冊(cè)到容器中 orderdBeanNames.forEach(beanName->{ registry.registerBeanDefinition(beanName, beans.get(beanName)); }); } }
注意:
執(zhí)行順序:Constructor > @Autowired > @PostConstruct
所以正常情況下,所有的bean都已經(jīng)執(zhí)行了構(gòu)造器Constructor,也就是bean都已經(jīng)存在容器中了。
也就是正常情況下,都是可以正常執(zhí)行了。
Autowired為null的情況如下:
(1)該類(lèi)沒(méi)有托管給spring 管理
一般在類(lèi)的上面添加@Component 就可以了
(2)不能new出來(lái)的實(shí)例,
例如:A a = new A();//new的對(duì)象不會(huì)交給Spring容器管理, 所以是不行的
特殊情況:@Configuration + @Bean 這種方式是可以的
二、Bean的執(zhí)行順序
注解@Order或者接口Ordered的作用是定義Spring IOC容器中Bean的執(zhí)行順序的優(yōu)先級(jí),而不是定義Bean的加載順序,Bean的加載順序不受@Order或Ordered接口的影響。
特別介紹
- @PropertySource:用于注解類(lèi),告訴當(dāng)前類(lèi)使用什么配置文件,配置文件必須是.property或者.xml類(lèi)型。
@PropertySource(value = {"classpath:config/user1.properties","classpath:config/user2.properties"})
- @ImportResource:通過(guò)配置文件注入bean,用于注解主配置類(lèi),導(dǎo)入一個(gè)或多個(gè)定義bean的配置文件,配置文件必須是.xml類(lèi)型。
@ImportResource(value = {"classpath:/beans.xml"}) @SpringBootApplication(scanBasePackages = {"team.seagull.client"}) public class DeployApplication { public static void main(String[] args) { SpringApplication.run(DeployApplication.class, args); } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用注解開(kāi)發(fā)SpringMVC詳細(xì)配置教程
這篇文章主要介紹了使用注解開(kāi)發(fā)SpringMVC詳細(xì)配置教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09java核心編程之文件過(guò)濾類(lèi)FileFilter和FilenameFilter
這篇文章主要為大家詳細(xì)介紹了java文件過(guò)濾類(lèi)FileFilter和FilenameFilter,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08詳解springMVC之與json數(shù)據(jù)交互方法
本篇文章主要介紹了詳解springMVC之與json數(shù)據(jù)交互方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05一文詳細(xì)講解Java時(shí)間格式轉(zhuǎn)換
這篇文章主要介紹了Java時(shí)間格式轉(zhuǎn)換的相關(guān)資料,文中詳細(xì)介紹了SimpleDateFormat(適用于Java8之前)和java.time(適用于Java8及之后)的用法,需要的朋友可以參考下2024-12-12Java中字符串轉(zhuǎn)int數(shù)據(jù)類(lèi)型的三種方式
這篇文章主要介紹了Java中字符串轉(zhuǎn)int數(shù)據(jù)類(lèi)型的三種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03在Java中解析JSON數(shù)據(jù)代碼示例及說(shuō)明
這篇文章主要介紹了在Java中解析JSON數(shù)據(jù)的相關(guān)資料,文中講解了如何使用Gson和Jackson庫(kù)解析JSON數(shù)據(jù),并展示了如何將日期時(shí)間字符串轉(zhuǎn)換為時(shí)間戳,通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03Java中類(lèi)的加載順序執(zhí)行結(jié)果
這篇文章主要介紹了Java中類(lèi)的加載順序執(zhí)行結(jié)果的相關(guān)資料,需要的朋友可以參考下2017-10-10Springboot教程之如何設(shè)置springboot熱重啟
這篇文章主要介紹了Springboot教程之如何設(shè)置springboot熱重啟,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07