关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】(中)

简介: 关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】(中)

ConfigurableEnvironment


扩展出了修改和配置profiles的一系列方法,包括用户自定义的和系统相关的属性。所有的环境实现类也都是它的实现~

// @since 3.1
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
  void setActiveProfiles(String... profiles);
  void addActiveProfile(String profile);
  void setDefaultProfiles(String... profiles);
  // 获取到所有的属性源~   MutablePropertySources表示可变的属性源们~~~ 它是一个聚合的  持有List<PropertySource<?>>
  // 这样获取出来后,我们可以add或者remove我们自己自定义的属性源了~
  MutablePropertySources getPropertySources();
  // 这里两个哥们应该非常熟悉了吧~~~
  Map<String, Object> getSystemProperties();
  Map<String, Object> getSystemEnvironment();
  // 合并两个环境配置信息~  此方法唯一实现在AbstractEnvironment上
  void merge(ConfigurableEnvironment parent);
}


它会有两个分支:


  1. ConfigurableWebEnvironment:显然它和web环境有关,提供方法void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig)让web自己做资源初始化~
  2. AbstractEnvironment:这个是重点,如下~


AbstractEnvironment


它是对环境的一个抽象实现,很重要。


public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  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";
  // 保留的默认的profile值   protected final属性,证明子类可以访问
  protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
  private final Set<String> activeProfiles = new LinkedHashSet<>();
  // 显然这个里面的值 就是default这个profile了~~~~
  private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
  // 这个很关键,直接new了一个 MutablePropertySources来管理属性源们
  // 并且是用的PropertySourcesPropertyResolver来处理里面可能的占位符~~~~~
  private final MutablePropertySources propertySources = new MutablePropertySources();
  private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
  // 唯一构造方法  customizePropertySources是空方法,交由子类去实现,对属性源进行定制~ 
  // Spring对属性配置分出这么多曾经,在SpringBoot中有着极其重要的意义~~~~
  public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
  }
  // 该方法,StandardEnvironment实现类是有复写的~
  protected void customizePropertySources(MutablePropertySources propertySources) {
  }
  // 若你想改变默认default这个值,可以复写此方法~~~~
  protected Set<String> getReservedDefaultProfiles() {
    return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
  }
  //  下面开始实现接口的方法们~~~~~~~
  @Override
  public String[] getActiveProfiles() {
    return StringUtils.toStringArray(doGetActiveProfiles());
  }
  protected Set<String> doGetActiveProfiles() {
    synchronized (this.activeProfiles) {
      if (this.activeProfiles.isEmpty()) { 
        // 若目前是empty的,那就去获取:spring.profiles.active
        String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(profiles)) {
          //支持,分隔表示多个~~~且空格啥的都无所谓
          setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
              StringUtils.trimAllWhitespace(profiles)));
        }
      }
      return this.activeProfiles;
    }
  }
  @Override
  public void setActiveProfiles(String... profiles) {
    synchronized (this.activeProfiles) {
      this.activeProfiles.clear(); // 因为是set方法  所以情况已存在的吧
      for (String profile : profiles) {
         // 简单的valid,不为空且不以!打头~~~~~~~~
        validateProfile(profile);
        this.activeProfiles.add(profile);
      }
    }
  }
  // default profiles逻辑类似,也是不能以!打头~
  @Override
  @Deprecated
  public boolean acceptsProfiles(String... profiles) {
    for (String profile : profiles) {
      // 此处:如果该profile以!开头,那就截断出来  把后半段拿出来看看   它是否在active行列里~~~ 
      // 此处稍微注意:若!表示一个相反的逻辑~~~~~请注意比如!dev表示若dev是active的,我反倒是不生效的
      if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
        if (!isProfileActive(profile.substring(1))) {
          return true;
        }
      } else if (isProfileActive(profile)) {
        return true;
      }
    }
    return false;
  }
  // 采用函数式接口处理  就非常的优雅了~
  @Override
  public boolean acceptsProfiles(Profiles profiles) {
    Assert.notNull(profiles, "Profiles must not be null");
    return profiles.matches(this::isProfileActive);
  }
  // 简答的说要么active包含,要门是default  这个profile就被认为是激活的
  protected boolean isProfileActive(String profile) {
    validateProfile(profile);
    Set<String> currentActiveProfiles = doGetActiveProfiles();
    return (currentActiveProfiles.contains(profile) ||
        (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
  }
  @Override
  public MutablePropertySources getPropertySources() {
    return this.propertySources;
  }
  public Map<String, Object> getSystemProperties() {
    return (Map) System.getProperties();
  }
  public Map<String, Object> getSystemEnvironment() {
    // 这个判断为:return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
    // 所以我们是可以通过在`spring.properties`这个配置文件里spring.getenv.ignore=false关掉不暴露环境变量的~~~
    if (suppressGetenvAccess()) {
      return Collections.emptyMap();
    }
    return (Map) System.getenv();
  }
  // Append the given parent environment's active profiles, default profiles and property sources to this (child) environment's respective collections of each.
  // 把父环境的属性合并进来~~~~  
  // 在调用ApplicationContext.setParent方法时,会把父容器的环境合并进来  以保证父容器的属性对子容器都是可见的
  @Override
  public void merge(ConfigurableEnvironment parent) {
    for (PropertySource<?> ps : parent.getPropertySources()) {
      if (!this.propertySources.contains(ps.getName())) {
        this.propertySources.addLast(ps); // 父容器的属性都放在最末尾~~~~
      }
    }
    // 合并active
    String[] parentActiveProfiles = parent.getActiveProfiles();
    if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
      synchronized (this.activeProfiles) {
        for (String profile : parentActiveProfiles) {
          this.activeProfiles.add(profile);
        }
      }
    }
    // 合并default
    String[] parentDefaultProfiles = parent.getDefaultProfiles();
    if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
      synchronized (this.defaultProfiles) {
        this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
        for (String profile : parentDefaultProfiles) {
          this.defaultProfiles.add(profile);
        }
      }
    }
  }
  // 其余方法全部委托给内置的propertyResolver属性,因为它就是个`PropertyResolver`
  ...
}


该抽象类完成了对active、default等相关方法的复写处理。它内部持有一个MutablePropertySources引用来管理属性源。


So,留给子类的活就不多了:只需要把你的属性源注册给我就OK了

StandardEnvironment

这个是Spring应用在非web容器运行的环境。从名称上解释为:标准实现


public class StandardEnvironment extends AbstractEnvironment {
  // 这两个值定义着  就是在@Value注解要使用它们时的key~~~~~
  /** System environment property source name: {@value}. */
  public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
  /** JVM system properties property source name: {@value}. */
  public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
  // 注册MapPropertySource和SystemEnvironmentPropertySource
  // SystemEnvironmentPropertySource是MapPropertySource的子类~~~~
  @Override
  protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
  }
}


StandardServletEnvironment


这是在web容器(servlet容器)时候的应用的标准环境。


public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
  public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
  public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
  public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
  // 放置三个web相关的配置源~  StubPropertySource是PropertySource的一个public静态内部类~~~
  @Override
  protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    // 可以通过spring.properties配置文件里面的spring.jndi.ignore=true关闭对jndi的暴露   默认是开启的
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
      propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    super.customizePropertySources(propertySources);
  }
  // 注册servletContextInitParams和servletConfigInitParams到属性配置源头里
  @Override
  public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
  }
}


注意:这里addFirst和addLast等关系这顺序,进而都关乎着配置最终的生效的。因此下面对比一下web环境和非web环境下属性源们的配置,各位要有感官上的一个认识~~~


非web环境:

image.png


web环境:

image.png


可见web相关配置的属性源的优先级是高于system相关的。

需要注意的是:若使用@PropertySource导入自定义配置,它会位于最底端(优先级最低

另外附上SpringBoot的属性源们:

访问:http://localhost:8080/env得到如下


image.png

相关文章
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3268 1
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
418 0
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
1034 0
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
510 18
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
591 0
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
851 1
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
779 0
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
在微服务架构中,随着业务复杂度增加,项目可能需要调用多个微服务。为避免使用`@Value`注解逐一引入配置的繁琐,可通过定义配置类(如`MicroServiceUrl`)并结合`@ConfigurationProperties`注解实现批量管理。此方法需在配置文件中设置微服务地址(如订单、用户、购物车服务),并通过`@Component`将配置类纳入Spring容器。最后,在Controller中通过`@Resource`注入配置类即可便捷使用,提升代码可维护性。
311 0
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1432 0
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1338 0

热门文章

最新文章

推荐镜像

更多
  • DNS