Spring 中的 Environment 、Profile 与 PropertySource(下)

简介: 如何理解 Environment?Environment 由 Spring 3.1 版本提出,表示当前应用的运行时环境。用于管理 Spring 中的条件配置 Profiles 和配置属性源。

Environment 的底层实现


Environment 接口的实现类


先来看 Environment 相关类图,对整体有一个了解。


image.png


PropertyResolver、Environment、ConfigurableEnvironment 在前面都有提到,剩下比较重要的类如下。


ConfigurablePropertyResolver:PropertyResolver 的子接口,在PropertyResolver 的基础上添加了设置类型转换服务 ConfigurableConversionService 及占位符前缀、后缀等方法。

AbstractEnvironment:Environment 的抽象实现,并且提供了自定义默认属性源的方法,下文将详细对其进行分析。

StandardEnvironment:非 web 环境下的标准实现,默认添加了表示系统属性的 PropertiesPropertySource 和 操作系统环境变量的 SystemEnvironmentPropertySource。

StandardServletEnvironment: web 环境下的标准实现,默认添加了 ServletConfigPropertySource 和 ServletContextPropertySource 两种属性源。


Profiles 管理实现


Environment Profiles 相关方法由 AbstractEnvironment 进行实现。部分代码如下。


public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  // active profiles 属性名
  public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
  // default profiles 属性名
  public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
  // active profiles 集合
  private final Set<String> activeProfiles = new LinkedHashSet<>();
  // default profiles 集合
  private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
  // 当前 Environment 接受的 profiles
  @Override
  public boolean acceptsProfiles(Profiles profiles) {
    Assert.notNull(profiles, "Profiles must not be null");
    return profiles.matches(this::isProfileActive);
  }
  // 给定名称的 profile 是否激活
  protected boolean isProfileActive(String profile) {
    validateProfile(profile);
    Set<String> currentActiveProfiles = doGetActiveProfiles();
    // 激活的 profiles 或默认的 profiles 中存在给定名称的 profile 则表示接受
    return (currentActiveProfiles.contains(profile) ||
        (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
  }
  //获取激活的 profiles
  protected Set<String> doGetActiveProfiles() {
    synchronized (this.activeProfiles) {
      if (this.activeProfiles.isEmpty()) {
        // 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
        String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(profiles)) {
          setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
              StringUtils.trimAllWhitespace(profiles)));
        }
      }
      return this.activeProfiles;
    }
  }
  // 获取默认的 profiles
  protected Set<String> doGetDefaultProfiles() {
    synchronized (this.defaultProfiles) {
      if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
        // 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
        String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(profiles)) {
          setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
              StringUtils.trimAllWhitespace(profiles)));
        }
      }
      return this.defaultProfiles;
    }
  }
}


可以看到,Profiles 的数据来源主要包括两块,通过 ConfigurableEnvironment API 设置的 profiles 优先,如果未设置则会从配置属性源中获取,然后进行缓存。


@Profile 注解的实现


Spring 4.0 之后对 @Profile 进行了重构,使用 @Conditional 实现,参见 Spring 条件注解 @Conditional 使用及其底层实现。@Profile 源码如下。


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
  /**
   * The set of profiles for which the annotated component should be registered.
   */
  String[] value();
}


可以看到,这里使用的条件是 ProfileCondition,源码如下。


class ProfileCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
      for (Object value : attrs.get("value")) {
        if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
          return true;
        }
      }
      return false;
    }
    return true;
  }
}


ProfileCondition 实现较为简单,如果 Environment 接受 @Profile 的值,则允许组件进行注入。


Environment 属性获取实现


以 AbstractEnvironment#getProperty(String) 获取属性的方法为例,代码如下。


public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  // 属性源
  private final MutablePropertySources propertySources = new MutablePropertySources();
  // 属性解析器
  private final ConfigurablePropertyResolver propertyResolver =
      new PropertySourcesPropertyResolver(this.propertySources);
  @Override
  @Nullable
  public String getProperty(String key) {
    return this.propertyResolver.getProperty(key);
  }
}


属性源由 MutablePropertySources 进行保存,而属性的获取则委托为 PropertySourcesPropertyResolver,这是 Spring 中常用的一种设计模式,类实现某一个接口,而实现委托给接口的另一个实现完成。再看 PropertySourcesPropertyResolver 源码。


public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
  // 属性源保存
  @Nullable
  private final PropertySources propertySources;
  // 构造方法
  public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
    this.propertySources = propertySources;
  }
  // 设置类型转换服务
  @Override
  public void setConversionService(ConfigurableConversionService conversionService) {
    this.propertyResolver.setConversionService(conversionService);
  }
  //设置占位符前缀
  @Override
  public void setPlaceholderPrefix(String placeholderPrefix) {
    this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
  }
  // 设置占位符后缀
  @Override
  public void setPlaceholderSuffix(String placeholderSuffix) {
    this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
  }
  // 获取属性值
  @Override
  @Nullable
  public String getProperty(String key) {
    return getProperty(key, String.class, true);
  }
  // 获取给定类型的属性值
  @Nullable
  protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
      // 循环从属性源中获取属性值
      for (PropertySource<?> propertySource : this.propertySources) {
        ... 省略部分代码
        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;
  }
}


PropertySourcesPropertyResolver 属性解析器循环属性源获取属性,有必要的情况下会解析属性值中的占位符,最后还会进行类型转换。类型转换为委托给 ConversionService,而占位符则有前缀、后缀。这些信息都在 AbstractEnvironment 中进行设置。


@ProperySource 注解的实现


@ProperySource 用来标注在配置类上,因此 @ProperySource 的处理在配置类的解析过程中。具体代码位置在ConfigurationClassParser#processPropertySource,这里简单进行分析。


  private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    // 先获取 @PropertySource 注解的属性值
    String name = propertySource.getString("name");
    if (!StringUtils.hasLength(name)) {
      name = null;
    }
    String encoding = propertySource.getString("encoding");
    if (!StringUtils.hasLength(encoding)) {
      encoding = null;
    }
    String[] locations = propertySource.getStringArray("value");
    Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
    boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
    Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
    // 默认使用 DefaultPropertySourceFactory 创建属性源的实例
    PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
        DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
    for (String location : locations) {
      try {
        String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
        Resource resource = this.resourceLoader.getResource(resolvedLocation);
        // 循环添加属性源
        addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
      } catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
        // Placeholders not resolvable or resource not found when trying to open it
        if (ignoreResourceNotFound) {
          if (logger.isInfoEnabled()) {
            logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
          }
        } else {
          throw ex;
        }
      }
    }
  }


处理 @PropertySource 过程,循环将资源文件位置转换为 EncodedResource,然后创建属性源,最后将属性源添加到 Environment 中。如果同时指定的属性源的名称和多个属性源的位置,还会将多个名称相同的属性源组合为一个 PropertySource。这里看一眼 DefaultPropertySourceFactory 的实现。


public class DefaultPropertySourceFactory implements PropertySourceFactory {
  @Override
  public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
    return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
  }
}


默认创建的 PropertySource 是 ResourcePropertySource,这个属性源会从 properties 或 xml 中加载属性,由于使用了 EncodedResource ,因此可以加载到正确编码的内容而不会乱码。有关 Resource 的文章可见 Spring 资源管理 (Resource)


@Value 如何注入属性

Spring 中 @Autowire 和 @Value 注解的处理都使用 AutowiredAnnotationBeanPostProcessor ,它会在 bean 实例化后对使用 @Autowire、@Value、@Inject 等注解标注的属性赋值,底层实现依赖AutowireCapableBeanFactory#resolveDependency。这块由于比较复杂,后面会再开一篇文章对其分析,感兴趣的小伙伴可先自行阅读源码。


总结

本篇首先对 Environment 的使用进行介绍,然后对其底层的实现进行分析。理解 Environment 同样有助于熟悉 Spring 的第三方框架源码,欢迎大家留言交流,感谢。


目录
相关文章
|
3月前
|
Java 测试技术 数据库
SpringBoot:@Profile注解和Spring EL
SpringBoot:@Profile注解和Spring EL
|
1月前
|
Java Spring
深入理解Spring Boot中的Profile配置
深入理解Spring Boot中的Profile配置
|
3月前
|
Java Shell 测试技术
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
73 2
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
|
3月前
|
存储 Java API
Spring揭秘:Environment接口应用场景及实现原理!
Environment接口提供了强大且灵活的环境属性管理能力,通过它,开发者能轻松地访问和配置应用程序运行时的各种属性,如系统属性、环境变量等。 同时,Environment接口还支持属性源的定制和扩展,使得开发者能根据实际需求灵活地定制属性的加载和解析方式。
146 1
Spring揭秘:Environment接口应用场景及实现原理!
|
3月前
|
XML Java 数据格式
掌握Spring Environment:配置管理的关键
掌握Spring Environment:配置管理的关键
193 1
|
3月前
|
Java Spring 容器
Spring注解驱动开发三切换环境Profile
Spring注解驱动开发三切换环境Profile
41 0
|
3月前
|
Java Spring 容器
Spring注解驱动开发二组件赋值-@Value和@PropertySource及XXXXAware接口
Spring注解驱动开发二组件赋值-@Value和@PropertySource及XXXXAware接口
37 0
|
3月前
|
XML Java 应用服务中间件
spring和maven(profile)的多环境部署
spring和maven(profile)的多环境部署
84 0
|
9月前
|
Java Spring
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
201 0
|
XML Java 数据格式