SpringBoot源码解析:Environment获取配置原理(2)

编程入门 行业动态 更新时间:2024-10-26 09:35:32

本篇基于SpringBoot 2.1.6源码解析

上一篇我们介绍了springboot怎么把配置文件的值加载到environment中。这篇我们介绍一下,怎么从environment中读取值

目录

        • 1. 传统方式获取environment配置
        • 2. 继承关系
        • 3. 源码解析
        • 4. ConfigurationPropertySourcesPropertySource name='configurationProperties'
        • 5. SpringBoot2.X 引入更强大的Binder获取environment配置方式
        • 6. 总结

SpringBoot源码解析:Environment加载配置原理(一)

1. 传统方式获取environment配置

  1. springboot启动类,传统从environment中获取值的方式environment.getProperty

    @SpringBootApplication(scanBasePackages = "com.zeng")
    public class ExampleStartApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext applicationContext = SpringApplication.run(ExampleStartApplication.class, args);
    
            ConfigurableEnvironment environment = applicationContext.getEnvironment();
            String serverPort = environment.getProperty("server.port");
            String boredCouponNo = environment.getProperty("bored.coupon.coupon-no");
            System.out.println(serverPort);
            System.out.println(boredCouponNo);
    
        }
    }
    
  2. application.yml

    server:
      port: 8080
    
    bored:
      coupon:
        coupon-no: 123456789
    
  3. 现在我们就可以debug一下。

2. 继承关系

我们先看看继承关系。

  1. PropertyResolver:此类就是用来获取解析(已经从配置文件中加载)PropertySources中的value。因为environment本身没有提供相应的获取配置的接口,自然没有解析配置值的方法。
  2. 为什么获取配置需要单独的接口来处理,我们可以看一下PropertyResolver的实现类AbstractPropertyResolver大致就知道,我们配置文件的value的值的类型转换,占位符的处理就要用到这个类

3. 源码解析

  1. 我们首先看一下AbstractEnvironment,实例化AbstractEnvironment就创建了一个propertyResolver。

    public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    	private final MutablePropertySources propertySources = new MutablePropertySources();
    
        //创建一个默认PropertySourcesPropertyResolver
    	private final ConfigurablePropertyResolver propertyResolver =
    			new PropertySourcesPropertyResolver(this.propertySources); 
        
    
        @Override
        @Nullable
        public String getProperty(String key) {
            //所以我们在获取配置的时候调用的就是PropertySourcesPropertyResolver的getProperty
            return this.propertyResolver.getProperty(key);
        }
    }
    
  2. PropertySourcesPropertyResolver的getProperty

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            //遍历之前解析加载的配置文件对象PropertySource,按顺序遍历获取配置,如果获取到就直接return。
            for (PropertySource<?> propertySource : this.propertySources) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Searching for key '" + key + "' in PropertySource '" +
                                 propertySource.getName() + "'");
                }
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    logKeyFound(key, propertySource, value);
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
    
        return null;
    }
    
  3. 以上代码this.propertySources结果如下。我们结合以上代码,可以看出spring从classpath或config等目录加载的所有配置其实都是一个propertySource对象保存,不存在覆盖的关系。获取配置的优先级就是从上到下遍历,获取到了,就不再往后遍历查询了。如果我们自定义了配置文件加载处理,可以设置优先级。

4. ConfigurationPropertySourcesPropertySource name=‘configurationProperties’

  1. debug的时候发现一个很有意思的事情,就是第二个元素configurationProperties中的ConfigurationPropertySourcesPropertySource 包含了外面所有的propertySource。

    所以在获取bored.coupon.coupon-no配置值的时候,当遍历到name=configurationProperties时获取到ConfigurationPropertySourcesPropertySource。

    然后在ConfigurationPropertySourcesPropertySource 中把这些配置又遍历了一遍,获取到之后就return了。

    并没有如我们想象的遍历到PropertySource序号7的元素applicationConfig

    ConfigurationPropertySourcesPropertySource源码如下:

    class ConfigurationPropertySourcesPropertySource extends PropertySource<Iterable<ConfigurationPropertySource>>
    		implements OriginLookup<String> {
    
    	ConfigurationPropertySourcesPropertySource(String name, Iterable<ConfigurationPropertySource> source) {
    		super(name, source);
    	}
    
    	@Override
    	public Object getProperty(String name) {
    		ConfigurationProperty configurationProperty = findConfigurationProperty(name);
    		return (configurationProperty != null) ? configurationProperty.getValue() : null;
    	}
    	
    	private ConfigurationProperty findConfigurationProperty(String name) {
    		try {
    			return findConfigurationProperty(ConfigurationPropertyName.of(name, true));
    		}
    		catch (Exception ex) {
    			return null;
    		}
    	}
    
    	private ConfigurationProperty findConfigurationProperty(ConfigurationPropertyName name) {
    		if (name == null) {
    			return null;
    		}
    		//最终在此处遍历的所有配置文件,获取的相关属性值
    		for (ConfigurationPropertySource configurationPropertySource : getSource()) {
    			ConfigurationProperty configurationProperty = configurationPropertySource.getConfigurationProperty(name);
    			if (configurationProperty != null) {
    				return configurationProperty;
    			}
    		}
    		return null;
    	}
    
    
  2. 疑惑,这是哪放进去的呢?前一篇有提到:SpringApplication的prepareEnvironment方法

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    		ApplicationArguments applicationArguments) {
    	//2.1 Create and configure the environment 创建和配置environment
    	ConfigurableEnvironment environment = getOrCreateEnvironment();
    	//2.2 加载启动命令行配置属性,active属性。(先不深入讲解)
    	configureEnvironment(environment, applicationArguments.getSourceArgs());
    	//2.3 发布事件
    	listeners.environmentPrepared(environment);
    	bindToSpringApplication(environment);
    	if (!this.isCustomEnvironment) {
    		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    				deduceEnvironmentClass());
    	}
    	//就是此处,把上述解析的所有配置文件PropertySources,添加到key为configurationProperties的PropertySource对象中,并添加到PropertySources的第一个元素中,我们可以深入一下看看
    	ConfigurationPropertySources.attach(environment);
    	return environment;
    }
    
    

    ConfigurationPropertySources.attach(environment);

    public static void attach(Environment environment) {
       Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
       //从environment中获取所有解析的配置MutablePropertySources
       MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
       PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
       if (attached != null && attached.getSource() != sources) {
          sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
          attached = null;
       }
       if (attached == null) {
       //把上述MutablePropertySources封装到ConfigurationPropertySourcesPropertySource,重新添加到MutablePropertySources中
          sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
       }
    }
    
  3. 为什么要引入ConfigurationPropertySourcesPropertySource?

    源码中看到直接通过name=configurationProperties获取ConfigurationPropertySourcesPropertySource 的暂时只有ConfigurationPropertySources类get方法,

    attach也是ConfigurationPropertySources这个类。

    而此处的get方法就是提供给Binder类使用(我们继续往下看)

    ConfigurationPropertySources源码如下:

    public static Iterable<ConfigurationPropertySource> get(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
        ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource) sources
            .get(ATTACHED_PROPERTY_SOURCE_NAME);
        if (attached == null) {
            return from(sources);
        }
        return attached.getSource();
    }
    

5. SpringBoot2.X 引入更强大的Binder获取environment配置方式

  1. 先看一个简单的示例,如何使用Binder绑定environment环境变量

    @SpringBootApplication(scanBasePackages = "com.zeng")
    public class ExampleStartApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext applicationContext = SpringApplication.run(ExampleStartApplication.class, args);
    
            ConfigurableEnvironment environment = applicationContext.getEnvironment();
            
            //我们看到此处调用了Binder.get方法获取了一个Binder
            Binder binder = Binder.get(environment);
    
            Integer port = binder.bind("server.port", Integer.class).get();
            //bind绑定yml文件中的配置key到一个具体的对象中
            Coupon coupon = binder.bind("bored.coupon", Coupon.class).get();
            System.out.println(port);
            System.out.println(coupon.toString());
    
        }
    }
    

    application.yml

    server:
      port: 8080
          
    bored:
      coupon:
        coupon-no: 123456789
        id: 12345656
        state: 0
        mcht-no: 987654321
    

    运行结果如下:

    8080
    Coupon(id=12345656, couponNo=123456789, mchtNo=987654321, state=0, createTime=null, updateTime=null)
    
  2. 通过以上示例,我们可以看到Binder可以很方便的动态绑定对象,类型转换。Binder还可以绑定Map,list等。我们可以发现源码中也有很多地方使用了Binder绑定配置对象,是一个非常好用强大的类。

  3. 源码看一下,我们看看Binder的get方法,就是调用了4.5中ConfigurationPropertySources.get方法,从environment中获取ConfigurationPropertySourcesPropertySource

    public static Binder get(Environment environment) {
        return new Binder(ConfigurationPropertySources.get(environment),
                          new PropertySourcesPlaceholdersResolver(environment));
    }
    

6. 总结

其实获取environment整篇相对逻辑简单,大家自行回顾一下。

更多推荐

SpringBoot源码解析:Environment获取配置原理(2)

本文发布于:2023-06-10 19:01:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/645252.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源码   原理   SpringBoot   Environment

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!