欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?boot?啟動(dòng)流程及外部化配置方法

 更新時(shí)間:2022年12月05日 11:43:08   作者:、楽.  
平時(shí)我們開發(fā)Spring boot 項(xiàng)目的時(shí)候,一個(gè)SpringBootApplication注解加一個(gè)main方法就可以啟動(dòng)服務(wù)器運(yùn)行起來,那它到底是怎么運(yùn)行起來的呢?這篇文章主要介紹了Spring?boot?啟動(dòng)流程及外部化配置,需要的朋友可以參考下

平時(shí)我們開發(fā)Spring boot 項(xiàng)目的時(shí)候,一個(gè)SpringBootApplication注解加一個(gè)main方法就可以啟動(dòng)服務(wù)器運(yùn)行起來,那它到底是怎么運(yùn)行起來的呢?

Main 入口

我們首先從main方法來看源碼,逐步深入:

@SpringBootApplication
public class AutoDemoApplication {

    public static void main(String[] args) {
        // run方法為入口
        SpringApplication.run(AutoDemoApplication.class, args);
    }

}
// 繼續(xù)調(diào)用 run 方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }
// 這個(gè)run 方法也很簡(jiǎn)單 做了兩件事
// 1. 實(shí)例化 SpringApplication 對(duì)象
// 2. 執(zhí)行對(duì)象的 run 方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

SpringApplication

那我們來看下SpringApplication這個(gè)對(duì)象里面做了什么:

public SpringApplication(Class<?>... primarySources) {
    this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 先把主類保存起來
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 判斷運(yùn)行項(xiàng)目的類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    
 // 初始化器:掃描當(dāng)前路徑下 META-INF/spring.factories 文件,加載ApplicationContextInitializer接口實(shí)例
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 設(shè)置監(jiān)聽器: 加載 ApplicationListener 接口實(shí)例
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

判斷運(yùn)行環(huán)境

構(gòu)造方法內(nèi)部會(huì)調(diào)用WebApplicationType.deduceFromClasspath()方法獲得應(yīng)用類型并設(shè)置當(dāng)前應(yīng)用是普通WEB應(yīng)用、響應(yīng)式web應(yīng)用(REACTIVE)還是非web應(yīng)用。

this.webApplicationType = WebApplicationType.deduceFromClasspath();

deduceFromClasspath方法由枚舉類WebApplicationType提供,具體實(shí)現(xiàn)如下:

static WebApplicationType deduceFromClasspath() {
    // 當(dāng)classpath 下只存在 org.springframework.web.reactive.DispatcherHandler
    // 不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
    // 則運(yùn)行模式為reactive 非阻塞模式
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
        return REACTIVE;
    } else {
        String[] var0 = SERVLET_INDICATOR_CLASSES;
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            String className = var0[var2];
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                // 普通應(yīng)用程序
                return NONE;
            }
        }
        // servlet 程序,需要加載并啟動(dòng)內(nèi)嵌的web服務(wù)
        return SERVLET;
    }
}

推斷的過程主要使用了ClassUtils.isPresent方法,用來判斷指定類名是否存在,是否可以進(jìn)行加載。源代碼如下:

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    } catch (IllegalAccessError var3) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
    } catch (Throwable var4) {
        return false;
    }
}

isPresent方法調(diào)用了forName方法,如果在調(diào)用過程中發(fā)生錯(cuò)誤則返回false,代表目標(biāo)類不存在。否則返回true。

我們看下 forName的部分代碼:

public static Class<?> forName(String name, @Nullable ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
    // 省略部分代碼
    
    		ClassLoader clToUse = classLoader;
            if (classLoader == null) {
                // 如果為空則使用默認(rèn)類加載器
                clToUse = getDefaultClassLoader();
            }

            try {
                // 返回加載的 class 
                return Class.forName(name, false, clToUse);
            } catch (ClassNotFoundException var9) {
                // 如果加載異常 則嘗試加載內(nèi)部類
                int lastDotIndex = name.lastIndexOf(46);
                if (lastDotIndex != -1) {
                    // 拼接內(nèi)部類
                    String nestedClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);

                    try {
                        return Class.forName(nestedClassName, false, clToUse);
                    } catch (ClassNotFoundException var8) {
                    }
                }

                throw var9;
            }
}

通過以上核心代碼,可得知 forName方法主要做的事情就是獲得類加載器,嘗試直接加載類,如果失敗則嘗試加載該類的內(nèi)部類,如果依舊失敗則拋出異常。

嘗試加載的類其實(shí)就是去掃描項(xiàng)目中引入的依賴,看看是否能加載到對(duì)應(yīng)的類。

因此,整個(gè)應(yīng)用類型的判斷分為以下幾個(gè)步驟:

  • spring boot 調(diào)用 SpringApplication構(gòu)造方法
  • SpringApplication構(gòu)造方法調(diào)用枚舉類的deduceFromClasspath方法
  • deduceFromClasspath方法通過ClassUtils.isPresent來判斷是否能成功加載指定的類
  • ClassUtils.isPresent方法通過調(diào)用forName方法來判斷是否能成功加載

初始化器和監(jiān)聽器

在構(gòu)造器中我們可以看到嘗試去加載兩個(gè)接口實(shí)例,都是利用SPI機(jī)制掃描META-INF/spring.factories文件實(shí)現(xiàn)的。

  • ApplicationContextInitializer

這個(gè)類當(dāng)spring boot 上下文Context初始化完成后會(huì)調(diào)用

  • ApplicationListener

當(dāng) spring boot 啟動(dòng)時(shí) 事件 change 都會(huì)觸發(fā);

該類是重中之重,比如 dubbo 、nacos集成 spring boot ,都是通過它進(jìn)行拓展的。

我們可以通過打斷點(diǎn)的方式來查看加載流程:

根據(jù) 接口名 作為 key 值去加載實(shí)現(xiàn)類,加載的時(shí)候可能分布在多個(gè) jar 包里面,如果我們自己自定義的話,也可以被加載進(jìn)去。

在這里插入圖片描述

這兩個(gè)類都是在spring boot 啟動(dòng)前執(zhí)行的,能幫我們進(jìn)行一些拓展操作。我們可以自定義初始化器,按照SPI機(jī)制即可:實(shí)現(xiàn)相關(guān)接口,同時(shí)在指定路徑下創(chuàng)建文件。

  • 我們首先創(chuàng)建一個(gè)初始化器以及監(jiān)聽器實(shí)現(xiàn)類:
public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("applicationContext 初始化完成 ... ");
    }
}
public class StarterApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println(event.toString());
        System.out.println("ApplicationListener .... " + System.currentTimeMillis());
    }
}
  • 創(chuàng)建指定文件 META-INF/spring.factories

key 值為接口全限定名 :value 值為實(shí)現(xiàn)類全限定名

org.springframework.context.ApplicationContextInitializer=\
 com.example.autodemo.listener.StarterApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  com.example.autodemo.listener.StarterApplicationListener

接口名實(shí)在找不到,點(diǎn)進(jìn)類里面自己拼裝一下即可。

在這里插入圖片描述

  • 重新運(yùn)行項(xiàng)目進(jìn)行測(cè)試

可以看到我們自定義的初始化器和監(jiān)聽器加載成功

在這里插入圖片描述

總結(jié)

如上就是 SpringApplication 初始化的代碼,基本上沒做啥事,主要就是判斷了一下運(yùn)行環(huán)境以及利用SPI機(jī)制加載META-INF/spring.factories下定義的事件監(jiān)聽器接口實(shí)現(xiàn)類。

執(zhí)行 run 方法

我們直接看 run 方法的源碼,逐步分析:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    // 不是重點(diǎn),設(shè)置環(huán)境變量
    this.configureHeadlessProperty();
    // 獲取事件監(jiān)聽器 SpringApplicationRunListeners 類型
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    // 執(zhí)行 start 方法
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 封裝參數(shù)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 重點(diǎn)來了嗷~~~
        // 準(zhǔn)備容器環(huán)境,會(huì)加載配置文件
        // 在這個(gè)方法里面會(huì)調(diào)用所有監(jiān)聽器Listeners的 onApplicationEvent(event),其中就有一個(gè)與配置文件相關(guān)的監(jiān)聽器被加載:ConfigFileApplicationListener
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        
        // 判斷環(huán)境的值,并設(shè)置一些環(huán)境的值
        this.configureIgnoreBeanInfo(environment);
        
        // 打印 banner 可以自定義
        Banner printedBanner = this.printBanner(environment);
        
        // 根據(jù)項(xiàng)目類型創(chuàng)建上下文 
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        
        // 準(zhǔn)備上下文 
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        
        // 重點(diǎn)流程?。。?
        // spring 的啟動(dòng)代碼 這里去掃描并且初始化單實(shí)例 bean ,同時(shí)啟動(dòng)了內(nèi)置web容器
        this.refreshContext(context);
        
        // 空的
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        // 執(zhí)行 AppilcationRunListeners 中的started方法
        listeners.started(context, timeTakenToStartup);
        // 執(zhí)行 runner
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }

    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

環(huán)境變量及配置

我們直接來看環(huán)境變量相關(guān)的實(shí)現(xiàn)流程:

prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 創(chuàng)建和配置環(huán)境變量
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach((Environment)environment);
    listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
    DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
    Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = this.convertEnvironment((ConfigurableEnvironment)environment);
    }

    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

剛進(jìn)來我們就可以看到一個(gè)核心方法,看名字我們就可以知道這里是去獲取或者創(chuàng)建環(huán)境,我們看看其源碼。

getOrCreateEnvironment

該方法根據(jù) webApplicationType判斷當(dāng)前項(xiàng)目是什么類型,這部分我們?cè)?code>SpringAlication中看到過:WebApplicationType.deduceFromClasspath()。

總共定義了三個(gè)類型:

  • None :應(yīng)用程序不作為WEB程序啟動(dòng),不啟動(dòng)內(nèi)嵌的服務(wù)。
  • SERVLET:應(yīng)用程序基于servlet的web應(yīng)用啟動(dòng),需啟動(dòng)內(nèi)嵌servlet服務(wù)。
  • REAVTIVE:應(yīng)用程序以響應(yīng)式web應(yīng)用啟動(dòng),需啟動(dòng)內(nèi)嵌響應(yīng)式web服務(wù)。
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    } else {
        switch(this.webApplicationType) {
        case SERVLET:
            return new ApplicationServletEnvironment();
        case REACTIVE:
            return new ApplicationReactiveWebEnvironment();
        default:
            return new ApplicationEnvironment();
        }
    }
}

由于我們是 servlet 項(xiàng)目,所以應(yīng)該調(diào)用 ApplicationServletEnvironment,如果版本不一致,名字應(yīng)該是:StandardServletEnvironment()方法。

ApplicationServletEnvironment繼承了 StandardServletEnvironment類,StandardServletEnvironment又繼承了StandardEnvironment類,StandardEnvironment又繼承了AbstractEnvironment類…(確實(shí)很多,但是到這里真沒了…)

最終在構(gòu)造方法中調(diào)用了customizePropertySources 方法:

public AbstractEnvironment() {
    this(new MutablePropertySources());
}

protected AbstractEnvironment(MutablePropertySources propertySources) {
    this.logger = LogFactory.getLog(this.getClass());
    this.activeProfiles = new LinkedHashSet();
    this.defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles());
    this.propertySources = propertySources;
    this.propertyResolver = this.createPropertyResolver(propertySources);
    this.customizePropertySources(propertySources);
}

這里我們進(jìn)去看,會(huì)發(fā)現(xiàn)是個(gè)空方法:

protected void customizePropertySources(MutablePropertySources propertySources) {
}

既然這里是空方法,那肯定是調(diào)用了StandardServletEnvironment中的方法:

protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }
    // 調(diào)用父類
    super.customizePropertySources(propertySources);
}

到這里位置,我們的propertySources中就加載了servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment,其中后兩個(gè)是從父類中添加的。

看到這里加載環(huán)境全部結(jié)束,有沒有發(fā)現(xiàn)一個(gè)問題?application配置文件好像沒有加進(jìn)來?不要急,其他相關(guān)的配置文件都是通過監(jiān)聽器拓展加入進(jìn)來的。

看了上面的代碼,那我們應(yīng)該如何將application配置文件添加到propertySources呢?是不是應(yīng)該有如下幾步:

  • 找文件:去磁盤中查找對(duì)應(yīng)的配置文件(這幾個(gè)目錄下:classpath、classpath/config、/、/config/、/*/config)
  • 讀文件內(nèi)容:IO流
  • 解析并封裝成對(duì)象
  • propertySources.addLast添加到最后

需要注意的是springboot 只會(huì)加載application命名的文件,大家以為的bootstrap在這里也不會(huì)被加載,不要問為什么,那是 cloud 中的文件,也是通過自定義監(jiān)聽器加進(jìn)去的,梳理完流程后我們可以自己手寫一個(gè)試試。

我們重新回到prepareEnvironment中,看看監(jiān)聽器相關(guān),最后會(huì)走到ConfigFileApplicationListener配置文件的監(jiān)聽器。入口在listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);

environmentPrepared

environmentPrepared

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {
        listener.environmentPrepared(bootstrapContext, environment);
    });
}

我們進(jìn)入監(jiān)聽器的environmentPrepared方法,最終會(huì)進(jìn)入到 SpringApplicationRunListener接口,這個(gè)接口在run方法中的getRunListeners里面獲取,最終在spring.factories里面加載實(shí)現(xiàn)類:EventPublishingRunListener,執(zhí)行它里面的environmentPrepared方法:

public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

multicastEvent

public void multicastEvent(ApplicationEvent event) {
    this.multicastEvent(event, this.resolveDefaultEventType(event));
}

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();
    Iterator var5 = this.getApplicationListeners(event, type).iterator();

    while(var5.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
            this.invokeListener(listener, event);
        }
    }

}

invokeListener

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = this.getErrorHandler();
    if (errorHandler != null) {
        try {
            this.doInvokeListener(listener, event);
        } catch (Throwable var5) {
            errorHandler.handleError(var5);
        }
    } else {
        this.doInvokeListener(listener, event);
    }

}

doInvokeListener

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    } catch (ClassCastException var6) {
        String msg = var6.getMessage();
        if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
            throw var6;
        }

        Log loggerToUse = this.lazyLogger;
        if (loggerToUse == null) {
            loggerToUse = LogFactory.getLog(this.getClass());
            this.lazyLogger = loggerToUse;
        }

        if (loggerToUse.isTraceEnabled()) {
            loggerToUse.trace("Non-matching event type for listener: " + listener, var6);
        }
    }

}

這個(gè)時(shí)候就可以進(jìn)入到我們的ConfigFileApplicationListener中了,看看是如何加載配置文件的。

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
    }

    if (event instanceof ApplicationPreparedEvent) {
        this.onApplicationPreparedEvent(event);
    }

}

onApplicationEnvironmentPreparedEvent方法里面讀取配置文件:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    Iterator var3 = postProcessors.iterator();

    while(var3.hasNext()) {
        EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }

}

進(jìn)入postProcessEnvironment方法:

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    this.addPropertySources(environment, application.getResourceLoader());
}

看到addPropertySources方法是不是就感到很熟悉了,這不就是我們的最終目的嗎,將文件添加到PropertySources中。

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    (new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
}

load方法是讀取配置文件的核心方法,需要走兩層,最后從loadWithFilteredProperties進(jìn)入,有的版本代碼是沒有分開的,這里按照自己的版本來。

void load() {
    FilteredPropertySource.apply(this.environment, "defaultProperties", ConfigFileApplicationListener.LOAD_FILTERED_PROPERTY, this::loadWithFilteredProperties);
}

private void loadWithFilteredProperties(PropertySource<?> defaultProperties) {
    this.profiles = new LinkedList();
    this.processedProfiles = new LinkedList();
    this.activatedProfiles = false;
    this.loaded = new LinkedHashMap();
    this.initializeProfiles();

    while(!this.profiles.isEmpty()) {
        ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
        if (this.isDefaultProfile(profile)) {
            this.addProfileToEnvironment(profile.getName());
        }

        this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
        this.processedProfiles.add(profile);
    }

    this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
    this.addLoadedPropertySources();
    this.applyActiveProfiles(defaultProperties);
}

文件監(jiān)聽代碼分析

代碼看的很亂,我們打斷點(diǎn)來逐步分析,從this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));處開始。

確定搜索路徑

點(diǎn)進(jìn)去可以發(fā)現(xiàn)這里再搜索路徑,到底搜索了那些路徑呢?

private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
    this.getSearchLocations().forEach((location) -> {
        String nonOptionalLocation = ConfigDataLocation.of(location).getValue();
        boolean isDirectory = location.endsWith("/");
        Set<String> names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
        names.forEach((name) -> {
            this.load(nonOptionalLocation, name, profile, filterFactory, consumer);
        });
    });
}

private Set<String> getSearchLocations() {
    Set<String> locations = this.getSearchLocations("spring.config.additional-location");
    if (this.environment.containsProperty("spring.config.location")) {
        locations.addAll(this.getSearchLocations("spring.config.location"));
    } else {
        locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/"));
    }

    return locations;
}

是我們上面說的五個(gè)路徑吧?以后別人問你從那幾個(gè)路徑可以讀取springboot 的配置文件,大家應(yīng)該都知道了吧,還能告訴他是怎么知道這五個(gè)路徑的,因?yàn)閮?nèi)部已經(jīng)將這五個(gè)路徑寫死了。

在這里插入圖片描述

路徑搜索

既然已經(jīng)確定了從那幾個(gè)路徑搜索文件,那么接下來就是怎么搜索了。

private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
    this.getSearchLocations().forEach((location) -> {
        boolean isDirectory = location.endsWith("/");
        Set<String> names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
        names.forEach((name) -> {
            this.load(location, name, profile, filterFactory, consumer);
        });
    });
}

首先是判斷是否為目錄,然后去搜索文件,上面我們說過了,springboot 只加載名字為 application 的文件,是因?yàn)樵诖a里面也寫死了。

private Set<String> getSearchNames() {
    if (this.environment.containsProperty("spring.config.name")) {
        String property = this.environment.getProperty("spring.config.name");
        Set<String> names = this.asResolvedSet(property, (String)null);
        names.forEach(this::assertValidConfigName);
        return names;
    } else {
        return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
    }
}

如果是目錄,也知道我們要查詢什么文件了,但是還要判斷它的后綴名,我們只接受 ymlpropertise后綴的文件。

private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
    if (!StringUtils.hasText(name)) {
        Iterator var13 = this.propertySourceLoaders.iterator();

        PropertySourceLoader loader;
        do {
            if (!var13.hasNext()) {
                throw new IllegalStateException("File extension of config file location '" + location + "' is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/'");
            }

            loader = (PropertySourceLoader)var13.next();
        } while(!this.canLoadFileExtension(loader, location));

        this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
    } else {
        Set<String> processed = new HashSet();
        Iterator var7 = this.propertySourceLoaders.iterator();

        while(var7.hasNext()) {
            PropertySourceLoader loaderx = (PropertySourceLoader)var7.next();
            String[] var9 = loaderx.getFileExtensions();
            int var10 = var9.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                String fileExtension = var9[var11];
                if (processed.add(fileExtension)) {
                    this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
                }
            }
        }

    }
}

這里我們關(guān)注getFileExtensions即可,因?yàn)橹挥羞@兩個(gè)實(shí)例,所以我們只處理這兩種后綴的。

在這里插入圖片描述

需要注意的是:當(dāng)某個(gè)配置既有ymlpropertise,那我們以propertise為準(zhǔn)。

添加進(jìn)PropertySources

上面文件搜索路徑找到文件后我們就是要將文件添加到PropertySources中了,這個(gè)時(shí)候回到核心方法處:

查看addLoadedPropertySources方法。

這代碼是不是也很簡(jiǎn)單,獲取到環(huán)境變量后,然后將我們剛剛加載的文件先封裝,然后在添加到最后。

這便是配置文件加載的全部操作。

private void addLoadedPropertySources() {
    MutablePropertySources destination = this.environment.getPropertySources();
    List<MutablePropertySources> loaded = new ArrayList(this.loaded.values());
    Collections.reverse(loaded);
    String lastAdded = null;
    Set<String> added = new HashSet();
    Iterator var5 = loaded.iterator();

    while(var5.hasNext()) {
        MutablePropertySources sources = (MutablePropertySources)var5.next();
        Iterator var7 = sources.iterator();

        while(var7.hasNext()) {
            PropertySource<?> source = (PropertySource)var7.next();
            if (added.add(source.getName())) {
                this.addLoadedPropertySource(destination, lastAdded, source);
                lastAdded = source.getName();
            }
        }
    }

}
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, PropertySource<?> source) {
    if (lastAdded == null) {
        if (destination.contains("defaultProperties")) {
            destination.addBefore("defaultProperties", source);
        } else {
            destination.addLast(source);
        }
    } else {
        destination.addAfter(lastAdded, source);
    }

}

自定義監(jiān)聽器

上面我們說了 bootstrap 文件就是通過監(jiān)聽器的形式加載進(jìn)來的,那我們也手寫一個(gè)監(jiān)聽器來記載文件。

我們還按照如下步驟來做不就可以了嘛。

  • 找文件:去磁盤中查找對(duì)應(yīng)的配置文件(這幾個(gè)目錄下:classpath、classpath/config、/、/config/、/*/config)
  • 讀文件內(nèi)容:IO流
  • 解析并封裝成對(duì)象
  • propertySources.addLast添加到最后

首先創(chuàng)建一個(gè)監(jiān)聽器的實(shí)現(xiàn)類:

需要注意的是泛型是ApplicationEnvironmentPreparedEvent類,不要問什么,你忘了我們看代碼的時(shí)候,在doInvokeListener中,文件監(jiān)聽器的觸發(fā)事件類型是ApplicationEnvironmentPreparedEvent嗎?所以想要查詢到我們的自定義文件,類型要設(shè)置正確。

代碼完全是按照我們上面的步驟來的,所以沒什么好說的。

public class MyPropertySourceListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {

        MutablePropertySources destination = event.getEnvironment().getPropertySources();
        Properties properties = new Properties();
        try {
            InputStream in = MyPropertySourceListener.class.getClassLoader().getResourceAsStream("test.properties");
            properties.load(in);
            ConcurrentHashMap<Object,Object> ch = new ConcurrentHashMap<>();
            for (Map.Entry entry : properties.entrySet()) {
                ch.put(entry.getKey(),entry.getValue());
            }
            OriginTrackedMapPropertySource source = new OriginTrackedMapPropertySource("test.properties",ch);
            destination.addLast(source);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

監(jiān)聽器寫完之后,我們需要將實(shí)現(xiàn)類配置到SPI里面:(我這里面有兩個(gè)自定義的初始化器和監(jiān)聽器,所以總共是三個(gè)了。)

org.springframework.context.ApplicationContextInitializer=\
 com.example.autodemo.listener.StarterApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  com.example.autodemo.listener.StarterApplicationListener,\
  com.example.autodemo.listener.MyPropertySourceListener

這個(gè)時(shí)候我們重新運(yùn)行項(xiàng)目,就可以看到我們的自定義監(jiān)聽器生效了,將文件加載進(jìn)來了。

通過這種方式加載進(jìn)來的配置信息我們就可以直接通過@Value注解讀取了,不需要在使用@ConfigurationProperties等注解了。

在這里插入圖片描述

refreshContext

該類啟動(dòng)spring的代碼加載了bean,同時(shí)啟動(dòng)了內(nèi)置的web容器,我們簡(jiǎn)單看下源碼流程:

一直往下看 refresh 即可,最后跳入AbstractApplicationContext類的refresh方法,可以發(fā)現(xiàn)這里就是spring 容器的啟動(dòng)代碼,還是那熟悉的12個(gè)流程:

該方法加載或者刷新一個(gè)持久化的配置,,可能是XML文件、屬性文件或關(guān)系數(shù)據(jù)庫(kù)模式。但是由于這是一種啟動(dòng)方式,如果失敗,那么應(yīng)該銷毀所有以及創(chuàng)建好的實(shí)例。換句話說:在調(diào)用該方法之后,要么全部實(shí)例化,要么完全不實(shí)例化。

public void refresh() throws BeansException, IllegalStateException {
    // 加載或刷新配置前的同步處理
    synchronized(this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 為刷新而準(zhǔn)備此上下文
        this.prepareRefresh();
        
        // 告訴子類去刷新內(nèi)部 bean 工廠
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 準(zhǔn)備好bean 工廠,以便再此上下文中使用
        this.prepareBeanFactory(beanFactory);

        try {
            // 允許在上下文子類中對(duì)bean工廠進(jìn)行后置處理
            this.postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 調(diào)用在上下文中注冊(cè)為bean的工廠處理器
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 注冊(cè)攔截bean創(chuàng)建的bean處理器
            this.registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
            // 初始化消息資源
            this.initMessageSource();
            // 初始化事件多路廣播器
            this.initApplicationEventMulticaster();
            // 初始話上下文子類中的其他特殊bean
            this.onRefresh();
            // 注冊(cè)監(jiān)聽器(檢查監(jiān)聽器的bean 并注冊(cè)他們)
            this.registerListeners();
            // 實(shí)例化所有剩余的單例 不包括懶加載的
            this.finishBeanFactoryInitialization(beanFactory);
            // 發(fā)布相應(yīng)的事件
            this.finishRefresh();
        } catch (BeansException var10) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
            }
            // 發(fā)生異常了銷毀所有已經(jīng)創(chuàng)建好的bean
            this.destroyBeans();
            // 重置 active 標(biāo)識(shí)
            this.cancelRefresh(var10);
            throw var10;
        } finally {
            // 重置內(nèi)核中的公用緩存
            this.resetCommonCaches();
            contextRefresh.end();
        }

    }
}

總結(jié)

以上便是spring boot 啟動(dòng)流程以及外部配置的所有內(nèi)容。

從啟動(dòng)流程來說:

  • 首先判斷項(xiàng)目類型
  • 初始化監(jiān)聽器和初始化器(可以理解為外部拓展)
  • 創(chuàng)建環(huán)境變量和上下文,還會(huì)創(chuàng)建一個(gè)配置文件的統(tǒng)一容器
  • 打印 banner
  • 執(zhí)行 spring 核心流程
  • 啟動(dòng) tomcat

從配置文件來說:

隨著springboot 啟動(dòng),會(huì)創(chuàng)建一個(gè)環(huán)境變量 environment ,在這個(gè)環(huán)境變量里面創(chuàng)建一個(gè)配置文件統(tǒng)一管理的容器,叫做PropertySources,創(chuàng)建好容器后,springboot 會(huì)基于監(jiān)聽器的形式從5個(gè)固定路徑下查找以application命名且拓展名為properties和yml的文件,并通過io流讀入內(nèi)存,最后通過不同的解析器進(jìn)行解析,解析完成后添加到最后。

到此這篇關(guān)于Spring boot 啟動(dòng)流程及外部化配置的文章就介紹到這了,更多相關(guān)Spring boot 啟動(dòng)流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springcloud gateway聚合swagger2的方法示例

    springcloud gateway聚合swagger2的方法示例

    這篇文章主要介紹了springcloud gateway聚合swagger2的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 詳解Java中switch的新特性

    詳解Java中switch的新特性

    這篇文章主要介紹了Java中switch的新特性,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 淺談SpringSecurity基本原理

    淺談SpringSecurity基本原理

    今天帶大家了解一下SpringSecurity的基本原理,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • Mybatis中的resultType和resultMap使用

    Mybatis中的resultType和resultMap使用

    這篇文章主要介紹了Mybatis中的resultType和resultMap使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)

    java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)

    這篇文章主要介紹了java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)包括了:配置線程池大小配置,線程池的實(shí)現(xiàn)原理等,需要的朋友可以參考下
    2023-02-02
  • dependencies導(dǎo)致的Maven依賴出錯(cuò)包紅問題解決方法

    dependencies導(dǎo)致的Maven依賴出錯(cuò)包紅問題解決方法

    多模塊和分布式開發(fā)一般都是有專門的的dependencies來進(jìn)行jar包的版本依賴問題,本文主要介紹了dependencies導(dǎo)致的Maven依賴出錯(cuò)包紅問題解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2022-05-05
  • Java BigInteger類,BigDecimal類,Date類,DateFormat類及Calendar類用法示例

    Java BigInteger類,BigDecimal類,Date類,DateFormat類及Calendar類用法示例

    這篇文章主要介紹了Java BigInteger類,BigDecimal類,Date類,DateFormat類及Calendar類用法,結(jié)合實(shí)例形式詳細(xì)分析了Java使用BigInteger類,BigDecimal類,Date類,DateFormat類及Calendar類進(jìn)行數(shù)值運(yùn)算與日期運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)

    Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)

    這篇文章主要為大家介紹了Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Spring Boot實(shí)現(xiàn)跨域訪問實(shí)現(xiàn)代碼

    Spring Boot實(shí)現(xiàn)跨域訪問實(shí)現(xiàn)代碼

    本文通過實(shí)例代碼給大家介紹了Spring Boot實(shí)現(xiàn)跨域訪問的知識(shí),然后在文中給大家介紹了spring boot 服務(wù)器端設(shè)置允許跨域訪問 的方法,感興趣的朋友一起看看吧
    2017-07-07
  • 深入理解Java中的字符串類型

    深入理解Java中的字符串類型

    這篇文章主要介紹了Java中的字符串類型,需要的朋友可以參考下
    2014-02-02

最新評(píng)論