深刻理解SpringCloud之引導程序應用上下文

  tips:我但願經過這篇文章來給對於bootstrap還不理解的朋友帶來幫助。固然這篇文章不只僅是講解知識,我更但願給廣大朋友帶來學習與理解官方文檔的一種思路。閱讀本文前,建議你們對SpringBoot的啓動機制與Environment的做用有大體的瞭解。關於SpringBoot的啓動機制咱們能夠參考:SpringBoot學習之啓動探究 html

  SpringCloud爲咱們提供了bootstrap.properties的屬性文件,咱們能夠在該屬性文件裏作咱們的服務配置。但是,咱們知道SpringBoot已經爲咱們提供了作服務配置的屬性文件application.properties,那麼這兩個配置文件有什麼區別呢?在SpringCloud裏是否能用bootstrap代替application作服務的配置?要解決這個問題,咱們必須先討論一下SpringCloud的引導。java

1、ConfigurableApplicationContext 的層級結構

1.一、層次結構的代碼分析

  ConfigurableApplicationContext是ApplicationContext的子接口,這裏面有一個方法叫setParent(), 該方法就的做用是設置它的父級ApplicationContext ,注意一旦設置了它的父上下文,後面就不能再次調用setParent方法了。究竟調用這個方法會產生什麼效果呢?下面咱們來看一下源代碼:ios

  AbstractApplicationContext的setParent:web

/**
     * Set the parent of this application context.
     * <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
     * {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
     * this (child) application context environment if the parent is non-{@code null} and
     * its environment is an instance of {@link ConfigurableEnvironment}.
     * @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
     */
    @Override
    public void setParent(ApplicationContext parent) {
        this.parent = parent;
        if (parent != null) {
            Environment parentEnvironment = parent.getEnvironment();
            if (parentEnvironment instanceof ConfigurableEnvironment) {
                getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
            }
        }
    }
View Code

  咱們能夠經過源代碼得知:一旦設置設置父上下文,當前的Environment會合並父上下文的Environment。spring

  GenericApplicationContext:express

//.......
/**
     * Create a new GenericApplicationContext with the given parent.
     * @param parent the parent application context
     * @see #registerBeanDefinition
     * @see #refresh
     */
    public GenericApplicationContext(ApplicationContext parent) {
        this();
        setParent(parent);
    }

// .....

/**
     * Set the parent of this application context, also setting
     * the parent of the internal BeanFactory accordingly.
     * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#setParentBeanFactory
     */
    @Override
    public void setParent(ApplicationContext parent) {
        super.setParent(parent);
        this.beanFactory.setParentBeanFactory(getInternalParentBeanFactory());
    }
View Code

  經過源代碼得知:該類不只會合並Environment還會把父上下文的BeanFactory"借用過來" ,咱們經常使用的ClasspathXmlApplicationContext是AbstractApplicationContext的子類,而AnnotationConfigApplicationContext是GenericApplicationContext的子類apache

1.二、演示示例

  首先咱們先建一個屬性文件application.properties,在屬性文件裏配置:bootstrap

jdbc.user=root

  而後咱們按照以下目錄創建好相關文件:服務器

  

  StudentConfig:app

package org.hzgj.spring.study.student;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan
@PropertySource("application.properties")
public class StudentConfig {
}
View Code

  TeacherConfig:

package org.hzgj.spring.study.teacher;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class TeacherConfig {
}
View Code

  Student:

package org.hzgj.spring.study.student;

import org.hzgj.spring.study.teacher.Teacher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Student {

    @Value("${jdbc.user}")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private int age=20;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    
}
View Code

  Teacher:

package org.hzgj.spring.study.teacher;

import org.springframework.stereotype.Component;

@Component
public class Teacher {

    private String name = "張老師";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
View Code

  Main方法:

package org.hzgj.spring.study;

import org.hzgj.spring.study.student.StudentConfig;
import org.hzgj.spring.study.student.Student;
import org.hzgj.spring.study.teacher.TeacherConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.naming.NamingException;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException, NamingException {

        AnnotationConfigApplicationContext studentApplicationContext = new AnnotationConfigApplicationContext(StudentConfig.class);
        AnnotationConfigApplicationContext teacherApplicationContext = new AnnotationConfigApplicationContext(TeacherConfig.class);
        teacherApplicationContext.setParent(studentApplicationContext);
        Student student = teacherApplicationContext.getBean(Student.class);
        System.out.println("獲取student對象的name屬性:" + student.getName());
        System.out.println(studentApplicationContext.getEnvironment().getProperty("jdbc.user"));
    }
}
View Code

  在這裏咱們將Teacher的父級上下文設置成student的,運行獲得以下結果:

 

2、SpringCloud引導上下文

  我在這裏先貼出官方文檔的一段描述:

引導應用程序上下文

  一個Spring Cloud應用程序經過建立一個「引導」上下文來進行操做,這個上下文是主應用程序的父上下文。開箱即用,負責從外部源加載配置屬性,還解密本地外部配置文件中的屬性。這兩個上下文共享一個Environment,這是任何Spring應用程序的外部屬性的來源。Bootstrap屬性的優先級高,所以默認狀況下不能被本地配置覆蓋。

引導上下文使用與主應用程序上下文不一樣的外部配置約定,所以使用bootstrap.yml application.yml(或.properties)代替引導和主上下文的外部配置。例:bootstrap.yml

spring:
  application:
    name: foo
  cloud:
    config:
      uri: ${SPRING_CONFIG_URI:http://localhost:8888}

若是您的應用程序須要服務器上的特定於應用程序的配置,那麼設置spring.application.name(在bootstrap.ymlapplication.yml)中是個好主意。

您能夠經過設置spring.cloud.bootstrap.enabled=false(例如在系統屬性中)來徹底禁用引導過程。

  初看這段話的朋友,可能會比較蒙圈,不要緊我來解釋幾個關鍵點:

  2.一、關於引導上下文在哪裏

        引導上下文,這個是什麼意思呢?咱們能夠把這個理解爲springcloud的"bios"。咱們能夠先看一下這個引導到底在哪裏:

  

  在這裏咱們能夠發現幾個關鍵的類,其中BootstrapApplicationListener是核心中的核心:我在這裏貼一下源代碼:

/*
 * Copyright 2013-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.bootstrap;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * A listener that prepares a SpringApplication (e.g. populating its Environment) by
 * delegating to {@link ApplicationContextInitializer} beans in a separate bootstrap
 * context. The bootstrap context is a SpringApplication created from sources defined in
 * spring.factories as {@link BootstrapConfiguration}, and initialized with external
 * config taken from "bootstrap.properties" (or yml), instead of the normal
 * "application.properties".
 *
 * @author Dave Syer
 *
 */
public class BootstrapApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";

    public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;

    public static final String DEFAULT_PROPERTIES = "defaultProperties";

    private int order = DEFAULT_ORDER;

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
                true)) {
            return;
        }
        // don't listen to events in a bootstrap context
        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }
        ConfigurableApplicationContext context = null;
        String configName = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
                .getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) {
                context = findBootstrapContext(
                        (ParentContextApplicationContextInitializer) initializer,
                        configName);
            }
        }
        if (context == null) {
            context = bootstrapServiceContext(environment, event.getSpringApplication(),
                    configName);
        }
        apply(context, event.getSpringApplication(), environment);
    }

    private ConfigurableApplicationContext findBootstrapContext(
            ParentContextApplicationContextInitializer initializer, String configName) {
        Field field = ReflectionUtils
                .findField(ParentContextApplicationContextInitializer.class, "parent");
        ReflectionUtils.makeAccessible(field);
        ConfigurableApplicationContext parent = safeCast(
                ConfigurableApplicationContext.class,
                ReflectionUtils.getField(field, initializer));
        if (parent != null && !configName.equals(parent.getId())) {
            parent = safeCast(ConfigurableApplicationContext.class, parent.getParent());
        }
        return parent;
    }

    private <T> T safeCast(Class<T> type, Object object) {
        try {
            return type.cast(object);
        }
        catch (ClassCastException e) {
            return null;
        }
    }

    private ConfigurableApplicationContext bootstrapServiceContext(
            ConfigurableEnvironment environment, final SpringApplication application,
            String configName) {
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment
                .getPropertySources();
        for (PropertySource<?> source : bootstrapProperties) {
            bootstrapProperties.remove(source.getName());
        }
        String configLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
        Map<String, Object> bootstrapMap = new HashMap<>();
        bootstrapMap.put("spring.config.name", configName);
        if (StringUtils.hasText(configLocation)) {
            bootstrapMap.put("spring.config.location", configLocation);
        }
        bootstrapProperties.addFirst(
                new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
        for (PropertySource<?> source : environment.getPropertySources()) {
            bootstrapProperties.addLast(source);
        }
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        List<String> names = SpringFactoriesLoader
                .loadFactoryNames(BootstrapConfiguration.class, classLoader);
        for (String name : StringUtils.commaDelimitedListToStringArray(
                environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
            names.add(name);
        }
        // TODO: is it possible or sensible to share a ResourceLoader?
        SpringApplicationBuilder builder = new SpringApplicationBuilder()
                .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
                .environment(bootstrapEnvironment)
                .properties("spring.application.name:" + configName)
                .registerShutdownHook(false).logStartupInfo(false).web(false);
        if (environment.getPropertySources().contains("refreshArgs")) {
            // If we are doing a context refresh, really we only want to refresh the
            // Environment, and there are some toxic listeners (like the
            // LoggingApplicationListener) that affect global static state, so we need a
            // way to switch those off.
            builder.application()
                    .setListeners(filterListeners(builder.application().getListeners()));
        }
        List<Class<?>> sources = new ArrayList<>();
        for (String name : names) {
            Class<?> cls = ClassUtils.resolveClassName(name, null);
            try {
                cls.getDeclaredAnnotations();
            }
            catch (Exception e) {
                continue;
            }
            sources.add(cls);
        }
        AnnotationAwareOrderComparator.sort(sources);
        builder.sources(sources.toArray(new Class[sources.size()]));
        final ConfigurableApplicationContext context = builder.run();
        // Make the bootstrap context a parent of the app context
        addAncestorInitializer(application, context);
        // It only has properties in it now that we don't want in the parent so remove
        // it (and it will be added back later)
        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

    private Collection<? extends ApplicationListener<?>> filterListeners(
            Set<ApplicationListener<?>> listeners) {
        Set<ApplicationListener<?>> result = new LinkedHashSet<>();
        for (ApplicationListener<?> listener : listeners) {
            if (!(listener instanceof LoggingApplicationListener)
                    && !(listener instanceof LoggingSystemShutdownListener)) {
                result.add(listener);
            }
        }
        return result;
    }

    private void mergeDefaultProperties(MutablePropertySources environment,
            MutablePropertySources bootstrap) {
        String name = DEFAULT_PROPERTIES;
        if (!bootstrap.contains(name)) {
            return;
        }
        PropertySource<?> source = bootstrap.get(name);
        if (source instanceof MapPropertySource) {
            Map<String, Object> map = ((MapPropertySource) source).getSource();
            // The application name is "bootstrap" (by default) at this point and
            // we don't want that to appear in the parent context at all.
            map.remove("spring.application.name");
        }
        if (!environment.contains(name)) {
            environment.addLast(source);
        }
        else {
            PropertySource<?> target = environment.get(name);
            if (target instanceof MapPropertySource) {
                Map<String, Object> targetMap = ((MapPropertySource) target).getSource();
                if (target == source) {
                    return;
                }
                if (source instanceof MapPropertySource) {
                    Map<String, Object> map = ((MapPropertySource) source).getSource();
                    for (String key : map.keySet()) {
                        if (!target.containsProperty(key)) {
                            targetMap.put(key, map.get(key));
                        }
                    }
                }
            }
        }
        mergeAdditionalPropertySources(environment, bootstrap);
    }

    private void mergeAdditionalPropertySources(MutablePropertySources environment,
            MutablePropertySources bootstrap) {
        PropertySource<?> defaultProperties = environment.get(DEFAULT_PROPERTIES);
        ExtendedDefaultPropertySource result = defaultProperties instanceof ExtendedDefaultPropertySource
                ? (ExtendedDefaultPropertySource) defaultProperties
                : new ExtendedDefaultPropertySource(defaultProperties.getName(),
                        defaultProperties);
        for (PropertySource<?> source : bootstrap) {
            if (!environment.contains(source.getName())) {
                result.add(source);
            }
        }
        for (String name : result.getPropertySourceNames()) {
            bootstrap.remove(name);
        }
        environment.replace(DEFAULT_PROPERTIES, result);
        bootstrap.replace(DEFAULT_PROPERTIES, result);
    }

    private void addAncestorInitializer(SpringApplication application,
            ConfigurableApplicationContext context) {
        boolean installed = false;
        for (ApplicationContextInitializer<?> initializer : application
                .getInitializers()) {
            if (initializer instanceof AncestorInitializer) {
                installed = true;
                // New parent
                ((AncestorInitializer) initializer).setParent(context);
            }
        }
        if (!installed) {
            application.addInitializers(new AncestorInitializer(context));
        }

    }

    private void apply(ConfigurableApplicationContext context,
            SpringApplication application, ConfigurableEnvironment environment) {
        @SuppressWarnings("rawtypes")
        List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
                ApplicationContextInitializer.class);
        application.addInitializers(initializers
                .toArray(new ApplicationContextInitializer[initializers.size()]));
        addBootstrapDecryptInitializer(application);
    }

    private void addBootstrapDecryptInitializer(SpringApplication application) {
        DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
        for (ApplicationContextInitializer<?> initializer : application
                .getInitializers()) {
            if (initializer instanceof EnvironmentDecryptApplicationInitializer) {
                @SuppressWarnings("unchecked")
                ApplicationContextInitializer<ConfigurableApplicationContext> delegate = (ApplicationContextInitializer<ConfigurableApplicationContext>) initializer;
                decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(
                        delegate);
            }
        }
        if (decrypter != null) {
            application.addInitializers(decrypter);
        }
    }

    private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context,
            Class<T> type) {
        List<T> result = new ArrayList<T>();
        for (String name : context.getBeanNamesForType(type)) {
            result.add(context.getBean(name, type));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    private static class AncestorInitializer implements
            ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

        private ConfigurableApplicationContext parent;

        public AncestorInitializer(ConfigurableApplicationContext parent) {
            this.parent = parent;
        }

        public void setParent(ConfigurableApplicationContext parent) {
            this.parent = parent;
        }

        @Override
        public int getOrder() {
            // Need to run not too late (so not unordered), so that, for instance, the
            // ContextIdApplicationContextInitializer runs later and picks up the merged
            // Environment. Also needs to be quite early so that other initializers can
            // pick up the parent (especially the Environment).
            return Ordered.HIGHEST_PRECEDENCE + 5;
        }

        @Override
        public void initialize(ConfigurableApplicationContext context) {
            while (context.getParent() != null && context.getParent() != context) {
                context = (ConfigurableApplicationContext) context.getParent();
            }
            reorderSources(context.getEnvironment());
            new ParentContextApplicationContextInitializer(this.parent)
                    .initialize(context);
        }

        private void reorderSources(ConfigurableEnvironment environment) {
            PropertySource<?> removed = environment.getPropertySources()
                    .remove(DEFAULT_PROPERTIES);
            if (removed instanceof ExtendedDefaultPropertySource) {
                ExtendedDefaultPropertySource defaultProperties = (ExtendedDefaultPropertySource) removed;
                environment.getPropertySources().addLast(new MapPropertySource(
                        DEFAULT_PROPERTIES, defaultProperties.getSource()));
                for (PropertySource<?> source : defaultProperties.getPropertySources()
                        .getPropertySources()) {
                    if (!environment.getPropertySources().contains(source.getName())) {
                        environment.getPropertySources().addBefore(DEFAULT_PROPERTIES,
                                source);
                    }
                }
            }
        }

    }

    /**
     * A special initializer designed to run before the property source bootstrap and
     * decrypt any properties needed there (e.g. URL of config server).
     */
    @Order(Ordered.HIGHEST_PRECEDENCE + 9)
    private static class DelegatingEnvironmentDecryptApplicationInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        private ApplicationContextInitializer<ConfigurableApplicationContext> delegate;

        public DelegatingEnvironmentDecryptApplicationInitializer(
                ApplicationContextInitializer<ConfigurableApplicationContext> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            this.delegate.initialize(applicationContext);
        }

    }

    private static class ExtendedDefaultPropertySource
            extends SystemEnvironmentPropertySource {

        private final CompositePropertySource sources;
        private final List<String> names = new ArrayList<>();

        public ExtendedDefaultPropertySource(String name,
                PropertySource<?> propertySource) {
            super(name, findMap(propertySource));
            this.sources = new CompositePropertySource(name);
        }

        public CompositePropertySource getPropertySources() {
            return this.sources;
        }

        public List<String> getPropertySourceNames() {
            return this.names;
        }

        public void add(PropertySource<?> source) {
            if (source instanceof EnumerablePropertySource
                    && !this.names.contains(source.getName())) {
                this.sources.addPropertySource(source);
                this.names.add(source.getName());
            }
        }

        @Override
        public Object getProperty(String name) {
            if (this.sources.containsProperty(name)) {
                return this.sources.getProperty(name);
            }
            return super.getProperty(name);
        }

        @Override
        public boolean containsProperty(String name) {
            if (this.sources.containsProperty(name)) {
                return true;
            }
            return super.containsProperty(name);
        }

        @Override
        public String[] getPropertyNames() {
            List<String> names = new ArrayList<>();
            names.addAll(Arrays.asList(this.sources.getPropertyNames()));
            names.addAll(Arrays.asList(super.getPropertyNames()));
            return names.toArray(new String[0]);
        }

        @SuppressWarnings("unchecked")
        private static Map<String, Object> findMap(PropertySource<?> propertySource) {
            if (propertySource instanceof MapPropertySource) {
                return (Map<String, Object>) propertySource.getSource();
            }
            return new LinkedHashMap<String, Object>();
        }

    }

}
View Code

  這個類是一個監聽器,它用於監聽ApplicationEnvironmentPreparedEvent事件,而EventPublishingRunListener在SpringBoot啓動時會觸發該事件。若是不理解的這個類的朋友請務必先了解SpringBoot啓動過程

  2.二、這個上下文是主應用程序的父上下文

    這個工做主要分爲兩個層面:1.建立上下文引導 2.設置爲其爲當前程序的父級上下文

      1) 咱們先看看onApplicationEvent方法,該方法首先讀取spring.cloud.bootstrap.enabled的屬性值若是爲false,那麼就直接return。這也就是官方文檔裏的說明能夠用此屬性禁用引導的理由。

      2)緊接着它會從當前應用程序SpringApplication試着在全部的ApplicationInitializer中獲取ParentContextApplicationContextInitializer,若是找到的話就把該類下的parent作爲引導上下文。

      3)若是沒有找到ParentContextApplicationContextInitializer,則經過 bootstrapServiceContext方法來建立引導上下文,其中以下代碼請你們留意下:

    List<String> names = SpringFactoriesLoader
                .loadFactoryNames(BootstrapConfiguration.class, classLoader);

         看到SpringFactoriesLoader不用想必定會在META-INF/spring.factoies裏找配置的BootstrapConfiguration的進行實例化

              4)經過以下代碼建立引導上下文對象:

SpringApplicationBuilder builder = new SpringApplicationBuilder()
                .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
                .environment(bootstrapEnvironment)
                .properties("spring.application.name:" + configName)
                .registerShutdownHook(false).logStartupInfo(false).web(false);
        if (environment.getPropertySources().contains("refreshArgs")) {
            // If we are doing a context refresh, really we only want to refresh the
            // Environment, and there are some toxic listeners (like the
            // LoggingApplicationListener) that affect global static state, so we need a
            // way to switch those off.
            builder.application()
                    .setListeners(filterListeners(builder.application().getListeners()));
        }
        List<Class<?>> sources = new ArrayList<>();
        for (String name : names) {
            Class<?> cls = ClassUtils.resolveClassName(name, null);
            try {
                cls.getDeclaredAnnotations();
            }
            catch (Exception e) {
                continue;
            }
            sources.add(cls);
        }
        AnnotationAwareOrderComparator.sort(sources);
        builder.sources(sources.toArray(new Class[sources.size()]));
        final ConfigurableApplicationContext context = builder.run();

  5)最後經過以下方法設置引導上下文爲當前應用程序的上下文:

// Make the bootstrap context a parent of the app context
        addAncestorInitializer(application, context);

  2.三、開箱即用,負責從外部源加載配置屬性,還解密本地外部配置文件中的屬性。

   開箱即用,理解起來很簡單。經過2.2分析,引導程序在SpringBoot的啓動前就幫咱們建立好了,固然也就開箱即用了。

   下面咱們看一下spring-cloud-context.jar下的META-INF/spring.factoies文件:

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
View Code

    咱們來看一下  BootstrapConfiguration下面配置的引導程序類:

    org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration:這個類主要解析加載外部化配置屬性

    org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration:主要配置文件中前綴爲{cipher}的相關解密,熟悉spring-boot-starter-security在springcloud應用的朋友必定不陌生

    org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration:主要是監聽EnvironmentChangeEvent事件用於刷新@ConfigurationProperties標記的配置

    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration:主要解析配置文件中的${}佔位符

  2.四、這兩個上下文共享一個Environment,這是任何Spring應用程序的外部屬性的來源。

    既然引導上下文爲當前主程序的父級上下文,那麼就能夠肯定他們共享Environment,至於爲何請閱讀文章第一部分

  2.五、Bootstrap屬性的優先級高,所以默認狀況下不能被本地配置覆蓋。

     要解釋這個咱們必須用代碼來演示了,結構圖:

    

    注意:MyBootstrapAutoConfiguration是咱們自定義的引導類,該類必定不能被@SpringBootApplication註解ComponentScan到,不然引導必然就會被主程序所覆蓋。所以我用包把他們區分開來

    MyBootstrapAutoConfiguration代碼:

package com.bdqn.lyrk.bootstrap.config;

import com.bdqn.lyrk.bootstrap.server.BootStrapConfig;
import com.bdqn.lyrk.bootstrap.server.Student;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(BootStrapConfig.class)
public class MyBootstrapAutoConfiguration {
    @Bean
    public Student student(BootStrapConfig bootStrapConfig){
        Student student = new Student();
        student.setName(bootStrapConfig.getName());
        return student;
    }
}
View Code

    BootstrapConfig:

package com.bdqn.lyrk.bootstrap.server;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("student")
public class BootStrapConfig {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
View Code

    Student:

package com.bdqn.lyrk.bootstrap.server;

public class Student {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
View Code

    application.yml:

student:
  name: application

    bootstrap.yml:

student:
  name: bootstrap

    spring.factories:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.bdqn.lyrk.bootstrap.config.MyBootstrapAutoConfiguration

      啓動類代碼:

package com.bdqn.lyrk.bootstrap.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication()
public class BootstrapServer {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(BootstrapServer.class, args);
        Student student = applicationContext.getBean(Student.class);
        System.out.println(student.getName());
    }
}
View Code

  運行後獲得結果:

 所以咱們能夠看到對於引導程序bootstrap.yml比application.yml優先級更高,更不可能被application.yml文件裏的所覆蓋

    

3、總結

  1)引導程序上下文在prepareEnvironment的階段就會被建立,建立時會讀取bootstrap.properties|yml 在內容做爲引導配置, 所以bootstrap優先於application加載。引導程序很是相似於bios,而bootstrap.application就至關於設置bios的相關參數

  2)boostrap的屬性文件在如下情景下會使用:

    配置中心:config-server裏請用bootstrap屬性文件

      解密屬性文件時,最好使用bootstrap屬性文件

    須要自定義引導程序時使用bootstrap屬性文件,主要必定不要被咱們主程序掃描到

  3)application會覆蓋bootstrap中的非引導配置,所以不建議兩種類型配置文件同時存在。簡單粗暴的作法是在springcloud應用中用bootstrap屬性文件代替application一統江湖嘛,畢竟Envrionment是共享的。

  4)  在閱讀官方文檔時,必定要結合源代碼深刻分析,才能更好的理解其用意

相關文章
相關標籤/搜索