本篇基于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配置
-
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); } }
-
application.yml
server: port: 8080 bored: coupon: coupon-no: 123456789
-
现在我们就可以debug一下。
2. 继承关系
我们先看看继承关系。
- PropertyResolver:此类就是用来获取解析(已经从配置文件中加载)PropertySources中的value。因为environment本身没有提供相应的获取配置的接口,自然没有解析配置值的方法。
- 为什么获取配置需要单独的接口来处理,我们可以看一下PropertyResolver的实现类AbstractPropertyResolver大致就知道,我们配置文件的value的值的类型转换,占位符的处理就要用到这个类
3. 源码解析
-
我们首先看一下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); } }
-
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; }
-
以上代码this.propertySources结果如下。我们结合以上代码,可以看出spring从classpath或config等目录加载的所有配置其实都是一个propertySource对象保存,不存在覆盖的关系。获取配置的优先级就是从上到下遍历,获取到了,就不再往后遍历查询了。如果我们自定义了配置文件加载处理,可以设置优先级。
4. ConfigurationPropertySourcesPropertySource name=‘configurationProperties’
-
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; }
-
疑惑,这是哪放进去的呢?前一篇有提到: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))); } }
-
为什么要引入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配置方式
-
先看一个简单的示例,如何使用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)
-
通过以上示例,我们可以看到Binder可以很方便的动态绑定对象,类型转换。Binder还可以绑定Map,list等。我们可以发现源码中也有很多地方使用了Binder绑定配置对象,是一个非常好用强大的类。
-
源码看一下,我们看看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)
发布评论