spring Environment上下文环境参数变量

spring通过Environment对象来存储上下文环境变量信息,即包含当前系统环境变量也包含配置文件配置变量。Environment作为一个bean被存放在容器中,可以在需要的地方进行依赖注入直接使用。

Environment的创建

以AnnotationConfigApplicationContext容器类来看,在其构造函数总会初始化reader = new AnnotatedBeanDefinitionReader(this);然后会调用重载构造函数AnnotatedBeanDefinitionReader(registry, getOrCreateEnvironment(registry)),最后通过getEnvironment获取environment。

AbstractApplicationContext#getEnvironment

复制代码
public ConfigurableEnvironment getEnvironment() {
   if (this.environment == null) {
      this.environment = createEnvironment();
   }
   return this.environment;
}
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
	}

这里最后看到创建了一个StandardEnvironment类型的实例。

Environment放入容器

在使用中,可以直接通过注解注入的方式将environment注入直接使用,如下:

复制代码
@Autowired
private Environment env;

那么environment什么时候装载到容器中,成为一个bean的呢?

这里还要回到容器初始化的Refresh方法,在初始化扫描到的bean前,会调用prepareBeanFactory()方法。

AbstractApplicationContext#prepareBeanFactory

复制代码
//...
// Register default environment beans.
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) {
   beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
}

这一步发生在所有的bean实例化之前,beanFactory.registerSingleton方法会将当前对象放到容器单例池里。也就是单例bean。可以说上面几个bean是最早一批bean。这里看到有environment、systemProperties、systemEnvironment和applicationStartup。

属性文件加载

在上一篇文章中介绍@Configuration时候可以通过@PropertySource进行属性配置文件的引入,引入的配置文件最终会放入environment中,可以通过environment对象获取属性文件内容。

如下:

复制代码
    @Autowired
    private Environment env;

    @PostConstruct
    public void init(){
        log.info("property name:{}",env.getProperty("name"));
    }

那么配置文件是怎么被存放到environment中的呢?在上面一步将environment放入spring容器的一步,所有的bean都还未实例化,@PropertySource也未初始化,这时候还未加载自定义引入的properties配置文件。

还是上一篇文章说的,容器会引入ConfigurationClassPostProcessor类来解析处理@Configuration注解,该类

不仅是一个bean定义后置处理器,还继承了EnvironmentAware接口。这样当ConfigurationClassPostProcessor在被容器实例化的时候,initializeBean()方法会调用invokeAwareInterfaces方法,这里会调到setEnvironment方法将容器的environment传递给当前ConfigurationClassPostProcessor。这样ConfigurationClassPostProcessor就拥有了容器的environment。

ApplicationContextAwareProcessor#invokeAwareInterfaces

复制代码
private void invokeAwareInterfaces(Object bean) {
   if (bean instanceof EnvironmentAware) {
      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
   }
   //...
}

下一步在ApplicationContextAwareProcessor中会使用ConfigurationClassParser类进行@PropertySource的解析,将解析到的property追加到environment的propertySources中。

具体逻辑在ConfigurationClassParser#addPropertySource()中。

最后来看下environment内部存储结构吧:

复制代码
{
   //存储Profile
  	private final Set<String> activeProfiles = new LinkedHashSet<>();
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
	//内部使用List<PropertySource<?>> propertySourceList来存储多个PropertySource
	private final MutablePropertySources propertySources;

	private final ConfigurablePropertyResolver propertyResolver;
}

Profile怎么理解呢,可以理成剖面,环境。举例说明就好理解了。

bean可以通过@Profile进行修饰,例如在数据源datasources初始化时候可以指定不同的Profile。

复制代码
@Profile("dev")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource getDuridSource(){
    return new DruidDataSource();
}
@Profile("sit")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource getDuridSource(){
    return new DruidDataSource();
}

如果一个bean被@Profile修饰,则只有当只有@Profile指定的值在activeProfiles之内,bean才会被加载。

profile的取值设定在

AbstractEnvironment#doGetActiveProfiles

复制代码
protected Set<String> doGetActiveProfiles() {
   synchronized (this.activeProfiles) {
      if (this.activeProfiles.isEmpty()) {
         String profiles = doGetActiveProfilesProperty();
         if (StringUtils.hasText(profiles)) {
            setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                  StringUtils.trimAllWhitespace(profiles)));
         }
      }
      return this.activeProfiles;
   }
}
	protected String doGetActiveProfilesProperty() {
		return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
	}

这里ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"。可以在启动时候通过-Dspring.profiles.active=xx来指定,也可以配置到properties文件中。都可以解析的到。

相关推荐
缉毒英雄祁同伟1 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
青云交1 小时前
Java 大视界 -- 基于 Java 的大数据可视化在能源互联网全景展示与能源调度决策支持中的应用
java·大数据可视化·智能决策·能源互联网·三维渲染·能源调度·nsga-ii
盖世英雄酱581361 小时前
国企“高级”程序员写的那些问题代码(六期)
java·后端
藤椒鱼不爱编程1 小时前
面向对象_类与对象
java
xcnwldgxxlhtff2 小时前
Java:线程池
java·开发语言
弹简特2 小时前
【Java web】HTTP 与 Web 基础教程
java·开发语言·前端
字节跳跃者2 小时前
Java 中的 Stream 可以替代 for 循环吗?
java·后端
北执南念2 小时前
如何在 Spring Boot 中设计和返回树形结构的组织和部门信息
java·spring boot·后端
遗憾皆是温柔3 小时前
19. 重载的方法能否根据返回值类型进行区分
java·开发语言·面试·学习方法
ts码农3 小时前
model层实现:
java·服务器·前端