成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Spring Boot 2.x 啟動全過程源碼分析(上)入口類剖析

MobService / 728人閱讀

摘要:設置應用上線文初始化器的作用是什么源碼如下。來看下方法源碼,其實就是初始化一個應用上下文初始化器實例的集合。設置監(jiān)聽器和設置初始化器調用的方法是一樣的,只是傳入的類型不一樣,設置監(jiān)聽器的接口類型為,對應的文件配置內容請見下方。

Spring Boot 的應用教程我們已經分享過很多了,今天來通過源碼來分析下它的啟動過程,探究下 Spring Boot 為什么這么簡便的奧秘。

本篇基于 Spring Boot 2.0.3 版本進行分析,閱讀本文需要有一些 Java 和 Spring 框架基礎,如果還不知道 Spring Boot 是什么,建議先看下我們的 Spring Boot 教程。

Spring Boot 的入口類
@SpringBootApplication
public class SpringBootBestPracticeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootBestPracticeApplication.class, args);
    }
    
}

做過 Spring Boot 項目的都知道,上面是 Spring Boot 最簡單通用的入口類。入口類的要求是最頂層包下面第一個含有 main 方法的類,使用注解 @SpringBootApplication 來啟用 Spring Boot 特性,使用 SpringApplication.run 方法來啟動 Spring Boot 項目。

來看一下這個類的 run 方法調用關系源碼:

public static ConfigurableApplicationContext run(Class primarySource,
        String... args) {
    return run(new Class[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class[] primarySources,
        String[] args) {
    return new SpringApplication(primarySources).run(args);
}

第一個參數(shù) primarySource:加載的主要資源類

第二個參數(shù) args:傳遞給應用的應用參數(shù)

先用主要資源類來實例化一個 SpringApplication 對象,再調用這個對象的 run 方法,所以我們分兩步來分析這個啟動源碼。

SpringApplication 的實例化過程

接著上面的 SpringApplication 構造方法進入以下源碼:

public SpringApplication(Class... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // 1、資源初始化資源加載器為 null
    this.resourceLoader = resourceLoader;
    
    // 2、斷言主要加載資源類不能為 null,否則報錯
    Assert.notNull(primarySources, "PrimarySources must not be null");
    
    // 3、初始化主要加載資源類集合并去重
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 4、推斷當前 WEB 應用類型
    this.webApplicationType = deduceWebApplicationType();
    
    // 5、設置應用上線文初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));        
            
    // 6、設置監(jiān)聽器            
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 7、推斷主入口應用類
    this.mainApplicationClass = deduceMainApplicationClass();
}

可知這個構造器類的初始化包括以下 7 個過程。

1、資源初始化資源加載器為 null
this.resourceLoader = resourceLoader;
2、斷言主要加載資源類不能為 null,否則報錯
Assert.notNull(primarySources, "PrimarySources must not be null");
3、初始化主要加載資源類集合并去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
4、推斷當前 WEB 應用類型
this.webApplicationType = deduceWebApplicationType();

來看下 deduceWebApplicationType 方法和相關的源碼:

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";

private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
        
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };        

public enum WebApplicationType {

    /**
     * 非 WEB 項目
     */
    NONE,

    /**
     * SERVLET WEB 項目
     */
    SERVLET,

    /**
     * 響應式 WEB 項目
     */
    REACTIVE

}

這個就是根據(jù)類路徑下是否有對應項目類型的類推斷出不同的應用類型。

5、設置應用上線文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));

ApplicationContextInitializer 的作用是什么?源碼如下。

public interface ApplicationContextInitializer {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

用來初始化指定的 Spring 應用上下文,如注冊屬性資源、激活 Profiles 等。

來看下 setInitializers 方法源碼,其實就是初始化一個 ApplicationContextInitializer 應用上下文初始化器實例的集合。

public void setInitializers(
        Collection> initializers) {
    this.initializers = new ArrayList<>();
    this.initializers.addAll(initializers);
}

再來看下這個初始化 getSpringFactoriesInstances 方法和相關的源碼:

private  Collection getSpringFactoriesInstances(Class type) {
    return getSpringFactoriesInstances(type, new Class[] {});
}

private  Collection getSpringFactoriesInstances(Class type,
        Class[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

設置應用上下文初始化器可分為以下 5 個步驟。

5.1)獲取當前線程上下文類加載器

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

5.2)獲取 ApplicationContextInitializer 的實例名稱集合并去重

Set names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 方法相關的源碼如下:

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

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

    try {
        Enumeration 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 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);
    }
}

根據(jù)類路徑下的 META-INF/spring.factories 文件解析并獲取 ApplicationContextInitializer 接口的所有配置的類路徑名稱。

spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 的初始化器相關配置內容如下:

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

5.3)根據(jù)以上類路徑創(chuàng)建初始化器實例列表

List instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);

private  List createSpringFactoriesInstances(Class type,
        Class[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set names) {
    List 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;
}

5.4)初始化器實例列表排序

AnnotationAwareOrderComparator.sort(instances);

5.5)返回初始化器實例列表

return instances;
6、設置監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

ApplicationListener 的作用是什么?源碼如下。

@FunctionalInterface
public interface ApplicationListener extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

看源碼,這個接口繼承了 JDK 的 java.util.EventListener 接口,實現(xiàn)了觀察者模式,它一般用來定義感興趣的事件類型,事件類型限定于 ApplicationEvent 的子類,這同樣繼承了 JDK 的 java.util.EventObject 接口。

設置監(jiān)聽器和設置初始化器調用的方法是一樣的,只是傳入的類型不一樣,設置監(jiān)聽器的接口類型為:getSpringFactoriesInstances,對應的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置內容請見下方。

# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.autoconfigure.BackgroundPreinitializer

可以看出目前只有一個 BackgroundPreinitializer 監(jiān)聽器。

7、推斷主入口應用類
this.mainApplicationClass = deduceMainApplicationClass();

private Class deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

這個推斷入口應用類的方式有點特別,通過構造一個運行時異常,再遍歷異常棧中的方法名,獲取方法名為 main 的棧幀,從來得到入口類的名字再返回該類。

總結

源碼分析內容有點多,也很麻煩,本章暫時分析到 SpringApplication 構造方法的初始化流程,下章再繼續(xù)分析其 run 方法,作者很快寫完過兩天就發(fā)布,掃碼關注下面的公眾號 "Java技術棧" 即可獲取推送更新。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://m.hztianpu.com/yun/76638.html

相關文章

  • 漲姿勢:Spring Boot 2.x 啟動過程源碼分析

    摘要:參考創(chuàng)建所有運行監(jiān)聽器并發(fā)布應用啟動事件來看下創(chuàng)建運行監(jiān)聽器相關的源碼創(chuàng)建邏輯和之前實例化初始化器和監(jiān)聽器的一樣,一樣調用的是方法來獲取配置的監(jiān)聽器名稱并實例化所有的類。 上篇《Spring Boot 2.x 啟動全過程源碼分析(一)入口類剖析》我們分析了 Spring Boot 入口類 SpringApplication 的源碼,并知道了其構造原理,這篇我們繼續(xù)往下面分析其核心 ru...

    suemi 評論0 收藏0
  • spring boot - 收藏集 - 掘金

    摘要:引入了新的環(huán)境和概要信息,是一種更揭秘與實戰(zhàn)六消息隊列篇掘金本文,講解如何集成,實現(xiàn)消息隊列。博客地址揭秘與實戰(zhàn)二數(shù)據(jù)緩存篇掘金本文,講解如何集成,實現(xiàn)緩存。 Spring Boot 揭秘與實戰(zhàn)(九) 應用監(jiān)控篇 - HTTP 健康監(jiān)控 - 掘金Health 信息是從 ApplicationContext 中所有的 HealthIndicator 的 Bean 中收集的, Spring...

    rollback 評論0 收藏0
  • SpringBoot源碼分析系列(一)--核心注解

    摘要:用于主類上最最最核心的注解,表示這是一個項目,用于開啟的各項能力。下面我們來分析一下這個注解的組成以及作用通過上面的代碼我們可以看出來是一個組合注解,主要由和這三個注解組成的。通過源碼可以看出也是一個組合注解。 ??SpringBoot項目一般都會有Application的入口類,入口類中會有main方法,這是一個標準的java應用程序的入口方法。@SpringBootApplicat...

    seanlook 評論0 收藏0
  • Spring Boot 最核心的 3 個注解詳解

    摘要:核心注解講解最大的特點是無需配置文件,能自動掃描包路徑裝載并注入對象,并能做到根據(jù)下的包自動配置。所以最核心的個注解就是這是添加的一個注解,用來代替配置文件,所有這個配置文件里面能做到的事情都可以通過這個注解所在類來進行注冊。 最近面試一些 Java 開發(fā)者,他們其中有些在公司實際用過 Spring Boot, 有些是自己興趣愛好在業(yè)余自己學習過。然而,當我問他們 Spring Boo...

    hzx 評論0 收藏0
  • Spring Boot 面試,一個問題就干趴下了!

    摘要:我又問微服務和有什么關系不用行不行然后對方就吱吱唔唔了可以打包部署,內部集成了。為什么說是自動配置的開啟注解是,其實它就是由下面三個注解組成的上面三個注解,前面兩個都是自帶的,和無關,所以說上面的回答的不是在點上。 最近棧長面試了不少人,其中不乏說對 Spring Boot 非常熟悉的,然后當我問到一些 Spring Boot 核心功能和原理的時候,沒人能說得上來,或者說不到點上,可以...

    junbaor 評論0 收藏0

發(fā)表評論

0條評論

MobService

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<