聊聊Spring的环境抽象Environment,以及配置@Profile使用详解(介绍profile的6种激活方式)【享学Spring】(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 聊聊Spring的环境抽象Environment,以及配置@Profile使用详解(介绍profile的6种激活方式)【享学Spring】(上)

前言


在我刚入行不久时,总是对上下文(Context)、环境(Environment)这类抽象概念搞不清楚、弄不明白、玩不转,更是不懂它哥俩的区别或者说是联系(说实话从中文上来说不好区分,至少我是这么认为的)。

直到现在,我可以根据自己的理解对这两者下个通俗易懂的定义(不喜勿喷):


  • 上下文:用来处理分层传递的抽象,代表着应用
  • 环境:当前上下文运行的环境,存储着各种全局变量。这些变量会影响着当前程序的运行情况(比如JDK信息、磁盘信息、内存信息等等。当然还包括用户自定义的一些属性值)

Spring属性管理API


其实在Spring3.1之前,在Spring中使用配置是有众多痛点的:比如多环境支持就是其中之一。直到Spring3.1,它提供了新的属性管理API,而且功能非常强大且很完善,以后对于一些属性信息(Properties)、配置信息(Profile)等都应该使用新的API来管理~


核心API主要包括下面4个部分:


  1. PropertySource:属性源。key-value属性对抽象
  2. PropertyResolver:属性解析器。用于解析相应key的value
  3. Profile:配置(资料里翻译为剖面,我实在不理解)。只有激活的配置profile的组件/配置才会注册到Spring容器,类似于maven中profile
  4. Environment:环境,本身也是个属性解析器PropertyResolver。它在基础上还提供了Profile特性,能够很好的对多环境支持。因此我们一般使用它,而不是底层接口PropertyResolver。 可以简单粗暴的把它理解为Profile 和 PropertyResolver 的组合

Spring3.1从这4个方面重新设计,使得API更加的清晰,而且功能也更加的强大了。


另外,关于PropertySource和PropertyResolver的说明,看官可先移步至:

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


关于属性(Properties)

PropertyResolver作为属性解析器,用于解析任何基础源的属性的接口。它提供了最基本的getProperty()和resolvePlaceholders()等操作接口~~~


ConfigurablePropertyResolver作为PropertyResolver的子接口,额外提供属性类型转换的功能,说得通俗点就是在基础上提供了转换所需的ConversionService。


注意:接口上一直都没有提供setProperty()等方法,因为它的设计是通过属性源PropertySource来存储和获取属性值的,而不是一个简单的Map而已~


AbstractPropertyResolver是对接口ConfigurablePropertyResolver的抽象实现。比如它确定了前缀、后缀:

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
  ...
  private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; // ${
  private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; // }
  @Nullable
  private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; // :
  ...
}


属性处理器在Spring内建唯一实现类为:PropertySourcesPropertyResolver

需要注意的是,环境抽象Environment关于属性部分的实现,也是委托给PropertySourcesPropertyResolver来处理的~


public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
  ...
  @Nullable
  private final PropertySources propertySources; //内部持有一组PropertySource
  // 由此可以看出propertySources的顺序很重要~~~
  // 并且还能处理占位符~~~~~ resolveNestedPlaceholders支持内嵌、嵌套占位符
  @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;
  }
  ...
}


PropertySources可以理解成一组PropertySource,它的唯一实现类为MutablePropertySources


public class MutablePropertySources implements PropertySources {
  // 内部确实是持有多个PropertySource
  private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
  public void addFirst(PropertySource<?> propertySource) {
    removeIfPresent(propertySource);
    this.propertySourceList.add(0, propertySource);
  }
  public void addLast(PropertySource<?> propertySource) {
    removeIfPresent(propertySource);
    this.propertySourceList.add(propertySource);
  }
  ... 
}


环境Environment和配置Profile


Environment表示当前应用程序正在运行的环境。

Environment接口继承自PropertyResolver,所以它既能处理属性值、也能处理配置Profile:


  1. properties的属性值由PropertyResolver定义和处理
  2. profile则表示当前的运行环境配置(剖面),对于应用程序中的 properties 而言,并不是所有的都会加载到系统中,只有其属性与 profile 匹配才会被激活加载


所以 Environment 对象的作用是确定哪些配置文件(如果有)profile 当前处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。


通过上面推荐博文里了解到了PropertySource,k-v属性值对系统非常重要且可以来自于多个地方:


  • 自定义的Properties文件
  • JVM系统属性
  • 系统环境变量
  • JNDI
  • ServeltContext、ServletConfig


Environment接口的定义如下:


// @since 3.1
public interface Environment extends PropertyResolver {
  // 返回此环境下激活的配置文件集
  String[] getActiveProfiles();
  // 如果未设置激活配置文件,则返回默认的激活的配置文件集
  String[] getDefaultProfiles();
  // @since 5.1
  @Deprecated
  boolean acceptsProfiles(String... profiles);
  boolean acceptsProfiles(Profiles profiles);
}

ConfigurableEnvironment


提供设置激活的 profile 和默认的 profile 的功能以及操作 Properties 的工具(委托实现)


public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    // 指定该环境下的 profile 集
    void setActiveProfiles(String... profiles);
    // 增加此环境的 profile
    void addActiveProfile(String profile);
    // 设置默认的 profile
    void setDefaultProfiles(String... profiles);
    // 返回此环境的 PropertySources
    MutablePropertySources getPropertySources();
   // 尝试返回 System.getenv() 的值,若失败则返回通过 System.getenv(string) 的来访问各个键的映射
    Map<String, Object> getSystemEnvironment();
    // 尝试返回 System.getProperties() 的值,若失败则返回通过 System.getProperties(string) 的来访问各个键的映射
    Map<String, Object> getSystemProperties();
    void merge(ConfigurableEnvironment parent);
}


该类除了继承 Environment 接口外还继承了 ConfigurablePropertyResolver 接口,所以它即具备了设置 profile 的功能也具备了操作 Properties 的功能。同时还允许客户端通过它设置和验证所需要的属性,自定义转换服务等功能。


AbstractEnvironment


它继承自ConfigurableEnvironment,它作为抽象实现,主要实现了profile相关功能。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
  // 请参考:ConfigurableEnvironment#setActiveProfiles
  public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
  // 请参考:ConfigurableEnvironment#setDefaultProfiles
  public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
  private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
  // 默认的profile名称
  protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
  ...
  protected Set<String> doGetActiveProfiles() {
    synchronized (this.activeProfiles) {
      if (this.activeProfiles.isEmpty()) {
        String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(profiles)) {
          setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
              StringUtils.trimAllWhitespace(profiles)));
        }
      }
      return this.activeProfiles;
    }
  }
  ...
}


如果 activeProfiles 为空,则从 Properties 中获取 spring.profiles.active 配置;如果不为空,则调用 setActiveProfiles() 设置 profile,最后返回。


从这里可以知道,API设置的activeProfiles优先级第一,其次才是属性配置(属性源是个很大的概念,希望各位看官能够理解)。


@Profile注解和ProfileCondition



上面介绍的都是API,其实Spring也非常友好的提供了注解的方式便捷的实现Profile能力:

// @since 3.1  能标注在类上、方法上~
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
  // 注意此处是个数组,也就是说可以激活多个的
  String[] value();
}


说明:spring-text包有个注解org.springframework.test.context.ActiveProfiles就是基于它的。


可以看到@Profile,从Spring4.0以后它的原理是基于org.springframework.context.annotation.Condition这个条件接口的,当然还少不了配套的@Conditional注解~~~


ProfileCondition

class ProfileCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 因为value值是个数组,所以此处有多个值 用的MultiValueMap
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
      for (Object value : attrs.get("value")) {
        // 多个值中,但凡只要有一个acceptsProfiles了,那就返回true~
        if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
          return true;
        }
      }
      return false;
    }
    return true;
  }
}


@Profile的value可以指定多个值,并且只需要有一个值符合了条件,@Profile标注的方法、类就会生效,就会被加入到容器内…


至于@Conditional是何时解析的,这部分知识在讲解Spring容器启动流程、初始化Bean的时候有N次提起,此处略


相关文章
|
8天前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
52 2
|
28天前
|
缓存 Java API
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
本文介绍了在Spring Boot中配置Swagger2的方法。通过创建一个配置类,添加`@Configuration`和`@EnableSwagger2`注解,使用Docket对象定义API文档的详细信息,包括标题、描述、版本和包路径等。配置完成后,访问`localhost:8080/swagger-ui.html`即可查看接口文档。文中还提示了可能因浏览器缓存导致的问题及解决方法。
64 0
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
|
28天前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
78 0
|
28天前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
26 0
|
28天前
|
SQL Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
101 0
|
2月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
211 26
|
5月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
94 0
|
4月前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
220 73
|
28天前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
104 0
|
28天前
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
39 0