基於SpringBoot的Environment源碼理解實現分散配置

前提

org.springframework.core.env.Environment是當前應用運行環境的公開接口,主要包括應用程序運行環境的兩個關鍵方面:配置文件(profiles)和屬性。Environment繼承自接口PropertyResolver,而PropertyResolver提供了屬性訪問的相關方法。這篇文章從源碼的角度分析Environment的存儲容器和加載流程,而後基於源碼的理解給出一個生產級別的擴展。java

本文較長,請用一個舒服的姿式閱讀。react

本文已經轉移到我的博客中維護,由於維護多個地方的內容太麻煩git

Environment類體系

spe-1

  • PropertyResolver:提供屬性訪問功能。
  • ConfigurablePropertyResolver:繼承自PropertyResolver,主要提供屬性類型轉換(基於org.springframework.core.convert.ConversionService)功能。
  • Environment:繼承自PropertyResolver,提供訪問和判斷profiles的功能。
  • ConfigurableEnvironment:繼承自ConfigurablePropertyResolver和Environment,而且提供設置激活的profile和默認的profile的功能。
  • ConfigurableWebEnvironment:繼承自ConfigurableEnvironment,而且提供配置Servlet上下文和Servlet參數的功能。
  • AbstractEnvironment:實現了ConfigurableEnvironment接口,默認屬性和存儲容器的定義,而且實現了ConfigurableEnvironment種的方法,而且爲子類預留可覆蓋了擴展方法。
  • StandardEnvironment:繼承自AbstractEnvironment,非Servlet(Web)環境下的標準Environment實現。
  • StandardServletEnvironment:繼承自StandardEnvironment,Servlet(Web)環境下的標準Environment實現。

reactive相關的暫時不研究。github

Environment提供的方法

通常狀況下,咱們在SpringMVC項目中啓用到的是StandardServletEnvironment,它的父接口問ConfigurableWebEnvironment,咱們能夠查看此接口提供的方法:web

spe-2

Environment的存儲容器

Environment的靜態屬性和存儲容器都是在AbstractEnvironment中定義的,ConfigurableWebEnvironment接口提供的getPropertySources()方法能夠獲取到返回的MutablePropertySources實例,而後添加額外的PropertySource。實際上,Environment的存儲容器就是org.springframework.core.env.PropertySource的子類集合,AbstractEnvironment中使用的實例是org.springframework.core.env.MutablePropertySources,下面看下PropertySource的源碼:spring

public abstract class PropertySource<T> {

    protected final Log logger = LogFactory.getLog(getClass());

    protected final String name;

    protected final T source;

    public PropertySource(String name, T source) {
        Assert.hasText(name, "Property source name must contain at least one character");
        Assert.notNull(source, "Property source must not be null");
        this.name = name;
        this.source = source;
    }

    @SuppressWarnings("unchecked")
    public PropertySource(String name) {
        this(name, (T) new Object());
    }

    public String getName() {
        return this.name;
    }

    public T getSource() {
        return this.source;
    } 

    public boolean containsProperty(String name) {
        return (getProperty(name) != null);
    } 

    @Nullable
    public abstract Object getProperty(String name);     

    @Override
    public boolean equals(Object obj) {
        return (this == obj || (obj instanceof PropertySource &&
                ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
    }  

    @Override
    public int hashCode() {
        return ObjectUtils.nullSafeHashCode(this.name);
    }  
//省略其餘方法和內部類的源碼            
}

源碼相對簡單,預留了一個getProperty抽象方法給子類實現,重點須要關注的是覆寫了的equalshashCode方法,實際上只和name屬性相關,這一點很重要,說明一個PropertySource實例綁定到一個惟一的name,這個name有點像HashMap裏面的key,部分移除、判斷方法都是基於name屬性。PropertySource的最經常使用子類是MapPropertySource、PropertiesPropertySource、ResourcePropertySource、StubPropertySource、ComparisonPropertySource:json

  • MapPropertySource:source指定爲Map實例的PropertySource實現。
  • PropertiesPropertySource:source指定爲Map實例的PropertySource實現,內部的Map實例由Properties實例轉換而來。
  • ResourcePropertySource:繼承自PropertiesPropertySource,source指定爲經過Resource實例轉化爲Properties再轉換爲Map實例。
  • StubPropertySource:PropertySource的一個內部類,source設置爲null,實際上就是空實現。
  • ComparisonPropertySource:繼承自ComparisonPropertySource,全部屬性訪問方法強制拋出異常,做用就是一個不可訪問屬性的空實現。

AbstractEnvironment中的屬性定義:數組

public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";

private final Set<String> activeProfiles = new LinkedHashSet<>();

private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);

上面的propertySources(MutablePropertySources類型)屬性就是用來存放PropertySource列表的,PropertySourcesPropertyResolver是ConfigurablePropertyResolver的實現,默認的profile就是字符串default。MutablePropertySources的內部屬性以下:app

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

沒錯,這個就是最底層的存儲容器,也就是環境屬性都是存放在一個CopyOnWriteArrayList<PropertySource<?>>實例中。MutablePropertySources是PropertySources的子類,它提供了get(String name)addFirstaddLastaddBeforeaddAfterremovereplace等便捷方法,方便操做propertySourceList集合的元素,這裏挑選addBefore的源碼分析:dom

public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
    if (logger.isDebugEnabled()) {
        logger.debug("Adding PropertySource '" + propertySource.getName() +
                    "' with search precedence immediately higher than '" + relativePropertySourceName + "'");
    }
    //前一個PropertySource的name指定爲relativePropertySourceName時候必須和添加的PropertySource的name屬性不相同
    assertLegalRelativeAddition(relativePropertySourceName, propertySource);
    //嘗試移除同名的PropertySource
    removeIfPresent(propertySource);
    //獲取前一個PropertySource在CopyOnWriteArrayList中的索引
    int index = assertPresentAndGetIndex(relativePropertySourceName);
    //添加當前傳入的PropertySource到指定前一個PropertySource的索引,至關於relativePropertySourceName對應的PropertySource後移到原來索引值+1的位置
    addAtIndex(index, propertySource);
}

protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
    String newPropertySourceName = propertySource.getName();
    if (relativePropertySourceName.equals(newPropertySourceName)) {
        throw new IllegalArgumentException(
                    "PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself");
    }
}

protected void removeIfPresent(PropertySource<?> propertySource) {
    this.propertySourceList.remove(propertySource);
}

private int assertPresentAndGetIndex(String name) {
    int index = this.propertySourceList.indexOf(PropertySource.named(name));
    if (index == -1) {
        throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
    }
    return index;
}

private void addAtIndex(int index, PropertySource<?> propertySource) {
    //注意,這裏會再次嘗試移除同名的PropertySource
    removeIfPresent(propertySource);
    this.propertySourceList.add(index, propertySource);
}

大多數PropertySource子類的修飾符都是public,能夠直接使用,這裏寫個小demo:

MutablePropertySources mutablePropertySources = new MutablePropertySources();
Map<String, Object> map = new HashMap<>(8);
map.put("name", "throwable");
map.put("age", 25);
MapPropertySource mapPropertySource = new MapPropertySource("map", map);
mutablePropertySources.addLast(mapPropertySource);
Properties properties = new Properties();
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("prop", properties);
properties.put("name", "doge");
properties.put("gourp", "group-a");
mutablePropertySources.addBefore("map", propertiesPropertySource);
System.out.println(mutablePropertySources);

Environment加載過程源碼分析

Environment加載的源碼位於SpringApplication#prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        //建立ConfigurableEnvironment實例
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //啓動參數綁定到ConfigurableEnvironment中
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //發佈ConfigurableEnvironment準備完畢事件
        listeners.environmentPrepared(environment);
        //綁定ConfigurableEnvironment到當前的SpringApplication實例中
        bindToSpringApplication(environment);
        //這一步是非SpringMVC項目的處理,暫時忽略
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        //綁定ConfigurationPropertySourcesPropertySource到ConfigurableEnvironment中,name爲configurationProperties,實例是SpringConfigurationPropertySources,屬性實際是ConfigurableEnvironment中的MutablePropertySources
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

這裏重點看下getOrCreateEnvironment方法:

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    //在SpringMVC項目,ConfigurableEnvironment接口的實例就是新建的StandardServletEnvironment實例
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();
    }
    return new StandardEnvironment();
}
//REACTIVE_WEB_ENVIRONMENT_CLASS=org.springframework.web.reactive.DispatcherHandler
//MVC_WEB_ENVIRONMENT_CLASS=org.springframework.web.servlet.DispatcherServlet
//MVC_WEB_ENVIRONMENT_CLASS={"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"}
//這裏,默認就是WebApplicationType.SERVLET
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;
}

還有一個地方要重點關注:發佈ConfigurableEnvironment準備完畢事件listeners.environmentPrepared(environment),實際上這裏用到了同步的EventBus,事件的監聽者是ConfigFileApplicationListener,具體處理邏輯是onApplicationEnvironmentPreparedEvent方法:

private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    //遍歷全部的EnvironmentPostProcessor對Environment實例進行處理
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
    }
}

//從spring.factories文件中加載,一共有四個實例
//ConfigFileApplicationListener
//CloudFoundryVcapEnvironmentPostProcessor
//SpringApplicationJsonEnvironmentPostProcessor
//SystemEnvironmentPropertySourceEnvironmentPostProcessor
List<EnvironmentPostProcessor> loadPostProcessors() {
    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
                getClass().getClassLoader());
}

實際上,處理工做大部分都在ConfigFileApplicationListener中,見它的postProcessEnvironment方法:

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

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

主要的配置環境加載邏輯在內部類Loader,Loader會匹配多個路徑下的文件把屬性加載到ConfigurableEnvironment中,加載器主要是PropertySourceLoader的實例,例如咱們用到application-${profile}.yaml文件作應用主配置文件,使用的是YamlPropertySourceLoader,這個時候activeProfiles也會被設置到ConfigurableEnvironment中。加載完畢以後,ConfigurableEnvironment中基本包含了全部須要加載的屬性(activeProfiles是這個時候被寫入ConfigurableEnvironment)。值得注意的是,幾乎全部屬性都是key-value形式存儲,如xxx.yyyy.zzzzz=value、xxx.yyyy[0].zzzzz=value-一、xxx.yyyy[1].zzzzz=value-2。Loader中的邏輯相對複雜,有比較多的遍歷和過濾條件,這裏不作展開。

Environment屬性訪問源碼分析

上文提到過,都是委託到PropertySourcesPropertyResolver,先看它的構造函數:

@Nullable
private final PropertySources propertySources;

public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
        this.propertySources = propertySources;
    }

只依賴於一個PropertySources實例,在SpringBoot的SpringMVC項目中就是MutablePropertySources的實例。重點分析一下最複雜的一個方法:

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        //遍歷全部的PropertySource
        for (PropertySource<?> propertySource : this.propertySources) {
            if (logger.isTraceEnabled()) {
                logger.trace("Searching for key '" + key + "' in PropertySource '" +
                            propertySource.getName() + "'");
            }
            Object value = propertySource.getProperty(key);
            //選用第一個不爲null的匹配key的屬性值
            if (value != null) {
                if (resolveNestedPlaceholders && value instanceof String) {
                    //處理屬性佔位符,如${server.port},底層委託到PropertyPlaceholderHelper完成
                    value = resolveNestedPlaceholders((String) value);
                }
                logKeyFound(key, propertySource, value);
                //若是須要的話,進行一次類型轉換,底層委託到DefaultConversionService完成
                return convertValueIfNecessary(value, targetValueType);
            }
        }
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Could not find key '" + key + "' in any property source");
    }
    return null;
}

這裏的源碼告訴咱們,若是出現多個PropertySource中存在同名的key,返回的是第一個PropertySource對應key的屬性值的處理結果,所以咱們若是須要自定義一些環境屬性,須要十分清楚各個PropertySource的順序。

擴展-實現分散配置

在不使用SpringCloud配置中心的狀況下,通常的SpringBoot項目的配置文件以下:

- src
 - main
  - resources
   - application-prod.yaml
   - application-dev.yaml
   - application-test.yaml

隨着項目發展,配置項愈來愈多,致使了application-${profile}.yaml迅速膨脹,大的配置文件甚至超過一千行,爲了簡化和劃分不一樣功能的配置,能夠考慮把配置文件拆分以下:

- src
 - main
  - resources
   - profiles
     - dev
       - business.yaml
       - mq.json
       - datasource.properties
     - prod
       - business.yaml
       - mq.json
       - datasource.properties
     - test  
       - business.yaml
       - mq.json  
       - datasource.properties
   - application-prod.yaml
   - application-dev.yaml
   - application-test.yaml

外層的application-${profile}.yaml只留下項目的核心配置如server.port等,其餘配置打散放在/profiles/${profile}/各自的配置文件中。實現方式是:依據當前配置的spring.profiles.active屬性,讀取類路徑中指定文件夾下的配置文件中,加載到Environment中,須要注意這一個加載步驟必須在Spring刷新上下文方法最後一步finishRefresh以前完成(這一點緣由能夠參考以前在我的博客寫過的SpringBoot刷新上下文源碼的分析),不然有可能會影響到佔位符屬性的自動裝配(例如使用了@Value("${filed}"))。

先定義一個屬性探索者接口:

public interface PropertySourceDetector {

    /**
     * 獲取支持的文件後綴數組
     *
     * @return String[]
     */
    String[] getFileExtensions();

    /**
     * 加載目標文件屬性到環境中
     *
     * @param environment environment
     * @param name        name
     * @param resource    resource
     * @throws IOException IOException
     */
    void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException;
}

而後須要一個抽象屬性探索者把Resource轉換爲字符串,額外提供Map的縮進、添加PropertySource到Environment等方法:

public abstract class AbstractPropertySourceDetector implements PropertySourceDetector {

    private static final String SERVLET_ENVIRONMENT_CLASS = "org.springframework.web."
            + "context.support.StandardServletEnvironment";

    public boolean support(String fileExtension) {
        String[] fileExtensions = getFileExtensions();
        return null != fileExtensions &&
                Arrays.stream(fileExtensions).anyMatch(extension -> extension.equals(fileExtension));
    }

    private String findPropertySource(MutablePropertySources sources) {
        if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && sources
                .contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
            return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;
        }
        return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
    }

    protected void addPropertySource(ConfigurableEnvironment environment, PropertySource<?> source) {
        MutablePropertySources sources = environment.getPropertySources();
        String name = findPropertySource(sources);
        if (sources.contains(name)) {
            sources.addBefore(name, source);
        } else {
            sources.addFirst(source);
        }
    }

    protected Map<String, Object> flatten(Map<String, Object> map) {
        Map<String, Object> result = new LinkedHashMap<>();
        flatten(null, result, map);
        return result;
    }

    private void flatten(String prefix, Map<String, Object> result, Map<String, Object> map) {
        String namePrefix = (prefix != null ? prefix + "." : "");
        map.forEach((key, value) -> extract(namePrefix + key, result, value));
    }

    @SuppressWarnings("unchecked")
    private void extract(String name, Map<String, Object> result, Object value) {
        if (value instanceof Map) {
            flatten(name, result, (Map<String, Object>) value);
        } else if (value instanceof Collection) {
            int index = 0;
            for (Object object : (Collection<Object>) value) {
                extract(name + "[" + index + "]", result, object);
                index++;
            }
        } else {
            result.put(name, value);
        }
    }

    protected String getContentStringFromResource(Resource resource) throws IOException {
        return StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8"));
    }
}

上面的方法參考SpringApplicationJsonEnvironmentPostProcessor,而後編寫各類類型配置屬性探索者的實現:

//Json
@Slf4j
public class JsonPropertySourceDetector extends AbstractPropertySourceDetector {

    private static final JsonParser JSON_PARSER = JsonParserFactory.getJsonParser();

    @Override
    public String[] getFileExtensions() {
        return new String[]{"json"};
    }

    @Override
    public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
        try {
            Map<String, Object> map = JSON_PARSER.parseMap(getContentStringFromResource(resource));
            Map<String, Object> target = flatten(map);
            addPropertySource(environment, new MapPropertySource(name, target));
        } catch (Exception e) {
            log.warn("加載Json文件屬性到環境變量失敗,name = {},resource = {}", name, resource);
        }
    }
}
//Properties
public class PropertiesPropertySourceDetector extends AbstractPropertySourceDetector {

    @Override
    public String[] getFileExtensions() {
        return new String[]{"properties", "conf"};
    }

    @SuppressWarnings("unchecked")
    @Override
    public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
        Map map = PropertiesLoaderUtils.loadProperties(resource);
        addPropertySource(environment, new MapPropertySource(name, map));
    }
}
//Yaml
@Slf4j
public class YamlPropertySourceDetector extends AbstractPropertySourceDetector {

    private static final JsonParser YAML_PARSER = new YamlJsonParser();

    @Override
    public String[] getFileExtensions() {
        return new String[]{"yaml", "yml"};
    }

    @Override
    public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
        try {
            Map<String, Object> map = YAML_PARSER.parseMap(getContentStringFromResource(resource));
            Map<String, Object> target = flatten(map);
            addPropertySource(environment, new MapPropertySource(name, target));
        } catch (Exception e) {
            log.warn("加載Yaml文件屬性到環境變量失敗,name = {},resource = {}", name, resource);
        }
    }
}

子類的所有PropertySource都是MapPropertySource,name爲文件的名稱,全部PropertySource都用addBefore方法插入到systemProperties的前面,主要是爲了提升匹配屬性的優先級。接着須要定義一個屬性探索者的合成類用來裝載全部的子類:

public class PropertySourceDetectorComposite implements PropertySourceDetector {

    private static final String DEFAULT_SUFFIX = "properties";
    private final List<AbstractPropertySourceDetector> propertySourceDetectors = new ArrayList<>();

    public void addPropertySourceDetector(AbstractPropertySourceDetector sourceDetector) {
        propertySourceDetectors.add(sourceDetector);
    }

    public void addPropertySourceDetectors(List<AbstractPropertySourceDetector> sourceDetectors) {
        propertySourceDetectors.addAll(sourceDetectors);
    }

    public List<AbstractPropertySourceDetector> getPropertySourceDetectors() {
        return Collections.unmodifiableList(propertySourceDetectors);
    }

    @Override
    public String[] getFileExtensions() {
        List<String> fileExtensions = new ArrayList<>(8);
        for (AbstractPropertySourceDetector propertySourceDetector : propertySourceDetectors) {
            fileExtensions.addAll(Arrays.asList(propertySourceDetector.getFileExtensions()));
        }
        return fileExtensions.toArray(new String[0]);
    }

    @Override
    public void load(ConfigurableEnvironment environment, String name, Resource resource) throws IOException {
        if (resource.isFile()) {
            String fileName = resource.getFile().getName();
            int index = fileName.lastIndexOf(".");
            String suffix;
            if (-1 == index) {
                //若是文件沒有後綴,看成properties處理
                suffix = DEFAULT_SUFFIX;
            } else {
                suffix = fileName.substring(index + 1);
            }
            for (AbstractPropertySourceDetector propertySourceDetector : propertySourceDetectors) {
                if (propertySourceDetector.support(suffix)) {
                    propertySourceDetector.load(environment, name, resource);
                    return;
                }
            }
        }
    }
}

最後添加一個配置類做爲入口:

public class PropertySourceDetectorConfiguration implements ImportBeanDefinitionRegistrar {

    private static final String PATH_PREFIX = "profiles";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) registry;
        ConfigurableEnvironment environment = beanFactory.getBean(ConfigurableEnvironment.class);
        List<AbstractPropertySourceDetector> propertySourceDetectors = new ArrayList<>();
        configurePropertySourceDetectors(propertySourceDetectors, beanFactory);
        PropertySourceDetectorComposite propertySourceDetectorComposite = new PropertySourceDetectorComposite();
        propertySourceDetectorComposite.addPropertySourceDetectors(propertySourceDetectors);
        String[] activeProfiles = environment.getActiveProfiles();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            for (String profile : activeProfiles) {
                String location = PATH_PREFIX + File.separator + profile + File.separator + "*";
                Resource[] resources = resourcePatternResolver.getResources(location);
                for (Resource resource : resources) {
                    propertySourceDetectorComposite.load(environment, resource.getFilename(), resource);
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    private void configurePropertySourceDetectors(List<AbstractPropertySourceDetector> propertySourceDetectors,
                                                  DefaultListableBeanFactory beanFactory) {
        Map<String, AbstractPropertySourceDetector> beansOfType = beanFactory.getBeansOfType(AbstractPropertySourceDetector.class);
        for (Map.Entry<String, AbstractPropertySourceDetector> entry : beansOfType.entrySet()) {
            propertySourceDetectors.add(entry.getValue());
        }
        propertySourceDetectors.add(new JsonPropertySourceDetector());
        propertySourceDetectors.add(new YamlPropertySourceDetector());
        propertySourceDetectors.add(new PropertiesPropertySourceDetector());
    }
}

準備就緒,在/resources/profiles/dev下面添加兩個文件app.json和conf:

//app.json
{
  "app": {
    "name": "throwable",
    "age": 25
  }
}
//conf
name=doge

項目的application.yaml添加屬性spring.profiles.active: dev,最後添加一個CommandLineRunner的實現用來觀察數據:

@Slf4j
@Component
public class CustomCommandLineRunner implements CommandLineRunner {

    @Value("${app.name}")
    String name;
    @Value("${app.age}")
    Integer age;
    @Autowired
    ConfigurableEnvironment configurableEnvironment;

    @Override
    public void run(String... args) throws Exception {
        log.info("name = {},age = {}", name, age);
    }
}

spe-3

自動裝配的屬性值和Environment實例中的屬性和預期同樣,改造是成功的。

小結

Spring中的環境屬性管理的源碼我的認爲是最清晰和簡單的:從文件中讀取數據轉化爲key-value結構,key-value結構存放在一個PropertySource實例中,而後獲得的多個PropertySource實例存放在一個CopyOnWriteArrayList中,屬性訪問的時候老是遍歷CopyOnWriteArrayList中的PropertySource進行匹配。可能相對複雜的就是佔位符的解析和參數類型的轉換,後者牽連到Converter體系,這些不在本文的討論範圍內。最後附上一張Environment存儲容器的示例圖:

spe-4

參考資料:

  • spring-boot-starter-web:2.0.3.RELEASE源碼。

示例項目:https://github.com/zjcscut/spring-boot-environment (本文完)

相關文章
相關標籤/搜索