04--SpringBoot啟動(dòng)之初始化與擴(kuò)展點(diǎn)加載機(jī)制

了解了SpringBoot啟動(dòng)類型判斷之后,接著分析SpringBoot一些內(nèi)部組件的初始化過程

//初始化Initializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//初始化Listener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

Dubbo中有通過ExtensionLoader來實(shí)現(xiàn)擴(kuò)展點(diǎn)加載的機(jī)制,在SpringBoot中也有類似的實(shí)現(xiàn),降低耦合

1. 擴(kuò)展點(diǎn)加載

//擴(kuò)展點(diǎn)加載
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[]{});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    //獲取當(dāng)前線程上下文類加載器
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    //獲取到的擴(kuò)展類名存入set集合防止重復(fù)
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //創(chuàng)建擴(kuò)展點(diǎn)實(shí)例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    //排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

加載擴(kuò)展點(diǎn)類名
SpringFactoriesLoader.loadFactoryNames(type, classLoader)

//通過給定類加載器獲得完全限定類名
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        //獲得factoryClass名稱
        String factoryClassName = factoryClass.getName();
        //加載類并返回默認(rèn)集合
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        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()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

SpringBoot的擴(kuò)展點(diǎn)存放在哪里呢,答案就在FACTORIES_RESOURCE_LOCATION靜態(tài)變量中
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

image.png

classLoader會(huì)加載當(dāng)前工程所有jar包(并非只有spring-boot一個(gè)工程)/META-INF/spring.factories文件,并從中找出要被加載的類,我們來看下ApplicationContextInitializer.class類在/META-INF/spring.factories對(duì)應(yīng)的信息,可以看到.factories文件的存儲(chǔ)方式為key:val0,val1,val2...

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#用來報(bào)告Spring容器的一些常見的錯(cuò)誤配置的
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#獲取應(yīng)用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
#委托給context.initializer.classes環(huán)境屬性下指定的其他初始值設(shè)定項(xiàng)。
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
#ApplicationContextInitializer設(shè)置服務(wù)器實(shí)際偵聽Environment的端口的屬性EmbeddedServletContainer。屬性“l(fā)ocal.server.port”可以直接注入使用 @Value或通過Environment。
#如果EmbeddedWebApplicationContext有一個(gè) namespace集合,它將用于構(gòu)造屬性名稱。例如,“管理”執(zhí)行器上下文將具有屬性名稱“l(fā)ocal.management.port”。屬性會(huì)自動(dòng)傳播到任何父上下文。
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

查看debug堆棧信息

image.png

除了spring-boot工程下的/META-INF/spring.factoriesApplicationContextInitializer被加載之外,還有org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializerorg.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer被加載,這兩個(gè)類信息位于spring-boot-autoconfigure工程的/META-INF/spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

至此,setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));所需要的類信息已經(jīng)全部加載

2.擴(kuò)展點(diǎn)實(shí)例化

//創(chuàng)建擴(kuò)展點(diǎn)實(shí)例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

//實(shí)例化擴(kuò)展點(diǎn),獲取與參數(shù)對(duì)應(yīng)的構(gòu)造方法,并實(shí)例化
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;
}

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));初始化監(jiān)聽器的方法與上述步驟相同
至此,我們已經(jīng)了解了SpringBoot的一些擴(kuò)展點(diǎn)是如何加載并實(shí)例化的,并且完成了SpringApplication的實(shí)例化工作

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容