SpringBoot深入淺出分析初始化器
如有錯(cuò)誤,望指正;
SpringBoot可以有三種方式定義初始化器,來為容器中增加自定義的對(duì)象,具體如下:
1、定義在spring.factories文件中,被SpringFactoriesLoader發(fā)現(xiàn)注冊(cè);
在resources下建立META-INF文件夾,新建spring.factories文件,添加自定義的初始化器:
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.InitializerOne
2、SpringApplication初始化完成后手動(dòng)添加;
SpringApplication springApplication = new SpringApplication(SbApplication.class);
springApplication.addInitializers(new InitializerTwo());
springApplication.run(args)
3、定義成環(huán)境變量,被DelegatingApplicationContextInitializer所發(fā)現(xiàn)注冊(cè)(優(yōu)先級(jí)最高)
application.properties中增加一項(xiàng)配置:
context.initializer.classes=com.mooc.sb2.initializer.ThirdInitializer
下面從代碼的角度,來看下這三種方式是如何加載的
對(duì)于第一種方法,可以跟進(jìn)
SpringApplication.run(SpringBootApplication.class, args);
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); }
繼續(xù)進(jìn)入這個(gè)run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
還有一個(gè)run方法,我們繼續(xù)進(jìn)入:
這個(gè)run方法代碼比較長(zhǎng),這里我們關(guān)注的是這部分:
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
我們進(jìn)入這個(gè)getSpringFactoriesInstances方法,如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
我們先看這個(gè)loadFactoryNames方法,它調(diào)用了loadSpringFactories方法來獲取固定位置的配置信息:
loadSpringFactories的重點(diǎn)部分代碼如下:
try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; }
我們可以發(fā)現(xiàn)一個(gè)常量的定義:FACTORIES_RESOURCE_LOCATION,而它的值,就是
"META-INF/spring.factories"
這里就解釋了我們?yōu)槭裁匆约航ㄒ粋€(gè)配置文件在固定的目錄下,這樣springboot就成功讀取到了自定義的初始化器,加載到了cache中;下面我們回到SpringApplication.java中,繼續(xù)跟進(jìn)getSpringFactoriesInstances方法。
在獲取到了初始化器的全限定名后,我們繼續(xù)看createSpringFactoriesInstances方法:
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }
主要使用了反射手段,來完成初始化器的實(shí)例化工作;在獲取了實(shí)例列表之AnnotationAwareOrderComparator.sort(instances);會(huì)將實(shí)例按照@Order的注解順序進(jìn)行排序,在這里不進(jìn)行詳述;
第二種初始化器是通過手動(dòng)添加
第三種是通過DelegatingApplicationContextInitializer的initialize方法來調(diào)用的,首先,我們先看這個(gè)方法的代碼:
@Override public void initialize(ConfigurableApplicationContext context) { ConfigurableEnvironment environment = context.getEnvironment(); List<Class<?>> initializerClasses = getInitializerClasses(environment); if (!initializerClasses.isEmpty()) { applyInitializerClasses(context, initializerClasses); } }
其中,我們重點(diǎn)關(guān)注getInitializerClasses()方法
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) { String classNames = env.getProperty(PROPERTY_NAME); List<Class<?>> classes = new ArrayList<>(); if (StringUtils.hasLength(classNames)) { for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) { classes.add(getInitializerClass(className)); } } return classes; }
這里的常量:
PROPERTY_NAME= "context.initializer.classes";
也就是我們配置文件的key,value值會(huì)被以‘,’為分界線進(jìn)行分割,來獲得每個(gè)所需的系統(tǒng)初始化器的全限定名,并通過BeanUtis工具來進(jìn)行初始化;
另:為什么DelegatingApplicationContextInitializer加載的初始化器是優(yōu)先于其他方式執(zhí)行呢?這是因?yàn)镾pringApplication的run方法中的prepareContext方法會(huì)調(diào)用applyInitializers方法,applyInitializers方法的for循環(huán)會(huì)調(diào)用getInitializers方法來加載所有的初始化器,而DelegatingApplicationContextInitializer的Order=0,因此優(yōu)先級(jí)最高,會(huì)被最先加載;
到此這篇關(guān)于SpringBoot深入淺出分析初始化器的文章就介紹到這了,更多相關(guān)SpringBoot初始化器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決
這篇文章主要介紹了java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Springboot整合SpringSecurity的完整案例詳解
Spring Security是基于Spring生態(tài)圈的,用于提供安全訪問控制解決方案的框架,Spring Security登錄認(rèn)證主要涉及兩個(gè)重要的接口 UserDetailService和UserDetails接口,本文對(duì)Springboot整合SpringSecurity過程給大家介紹的非常詳細(xì),需要的朋友參考下吧2024-01-01簡(jiǎn)單了解Spring循環(huán)依賴解決過程
這篇文章主要介紹了簡(jiǎn)單了解Spring循環(huán)依賴解決過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11java面向?qū)ο笤O(shè)計(jì)原則之單一職責(zé)與依賴倒置原則詳解
這篇文章主要介紹了java面向?qū)ο笤O(shè)計(jì)原則之單一職責(zé)與依賴倒置原則的分析詳解,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進(jìn)步早日升職加薪2021-10-10java利用注解實(shí)現(xiàn)簡(jiǎn)單的excel數(shù)據(jù)讀取
這篇文章主要為大家詳細(xì)介紹了java利用注解實(shí)現(xiàn)簡(jiǎn)單的excel數(shù)據(jù)讀取,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06java并發(fā)高的情況下用ThreadLocalRandom來生成隨機(jī)數(shù)
如果我們想要生成一個(gè)隨機(jī)數(shù),通常會(huì)使用Random類。但是在并發(fā)情況下Random生成隨機(jī)數(shù)的性能并不是很理想,本文主要介紹了java并發(fā)高的情況下用ThreadLocalRandom來生成隨機(jī)數(shù),感興趣的可以了解一下2022-05-05IDEA2020 Plugins不能用的解決辦法及Plugins 搜索不了插件的問題
這篇文章主要介紹了IDEA2020 Plugins不能用的解決辦法,文中給大家介紹了Intellij IDEA 2020.1 的Plugins 搜索不了插件,連接超時(shí)的問題,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-06-06