前言

在我刚入行不久时,总是对上下文(Context)、环境(Environment)这类抽象概念搞不清楚、弄不明白、玩不转,更是不懂它哥俩的区别或者说是联系(说实话从中文上来说不好区分,至少我是这么认为的)。
直到现在,我可以根据自己的理解对这两者下个通俗易懂的定义(不喜勿喷):

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

Spring属性管理API

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

PropertySource:属性源。key-value属性对抽象
PropertyResolver:属性解析器。用于解析相应key的value
Profile:配置(资料里翻译为剖面,我实在不理解)。只有激活的配置profile的组件/配置才会注册到Spring容器,类似于maven中profile
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:

properties的属性值由PropertyResolver定义和处理
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次提起,此处略

Profile使用的示例分析
在项目开发中,很多配置它在开发环境和线上环境是不一样的,最为典型就是数据库连接、redis连接等。
一般来说,最次都会有两种环境(公司越大、项目越复杂,环境会越多~):

开发环境dev
生产环境prod

本文就以这两个环境为基础,用一个非常简单的例子来演示profile的使用:
@Configuration
public class RootConfig {

@Profile({"default"}) // 显示的指出defualt~~~
    @Bean("person")
    public Person personDefault() {
        return new Person("我代表defualt数据源", 0);
    }

@Profile({"dev", "test"})
    @Bean("person")
    public Person personDev() {
        return new Person("我代表dev数据源", 66);
    }

@Profile({"prod", "online"})
    @Bean("person")
    public Person personProd() {
        return new Person("我代表prod数据源", 88);
    }

}

验证:
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class);

Environment environment = context.getEnvironment();
        String[] activeProfiles = environment.getActiveProfiles();
        String[] defaultProfiles = environment.getDefaultProfiles();

System.out.println(ArrayUtils.toString(activeProfiles)); //{}
        System.out.println(ArrayUtils.toString(defaultProfiles)); //{default}
        System.out.println(context.getBean("person"));

}

结果打印:
{}
{default}
Person{name='我代表defualt数据源', age=0}

由于我没有手动设置过任何激活Profiles,所以走默认的,因此最终被放进容器的Person对象就是我们那个@Profile({"default"})默认的。
手动设置激活的Profiles:
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

// 手动设置激活的Profiles
        ConfigurableEnvironment environment = context.getEnvironment();
        environment.setActiveProfiles("dev", "test1", "test2");
        // environment.setDefaultProfiles("",""); // 也可以设置默认的  只是一般都不这么干~

context.register(RootConfig.class);
        context.refresh();

String[] activeProfiles = environment.getActiveProfiles();
        String[] defaultProfiles = environment.getDefaultProfiles();
        System.out.println(ArrayUtils.toString(activeProfiles)); //{}
        System.out.println(ArrayUtils.toString(defaultProfiles)); //{default}
        System.out.println(context.getBean("person"));

}

结果打印:
{dev,test1,test2}
{default}
Person{name='我代表dev数据源', age=66}

结果符合我们预期。这里需要注意的是:environment.setActiveProfiles()方法必须在容器启动之前调用,否则无效。
(激活prod的做法一样,此处就无需再演示了~~~)
Tips:若类/方法上没有标注任何@Profile注解的,不受到activeProfiles的影响,都会被加入到容器。

激活profile的6种方式
上面示例介绍的是自己手动API调用方式去激活profile,但在实际开发中,这样做显得非常的麻烦,而且并不是每位小伙伴都知道这个API和调用时机,使用门槛偏高。
Spring考虑到了这一点,所以它提供了非常多的方式让你可以激活profile,任君灵活选择。本文我介绍如下6种方式:
方式一:API调用方式
见上例
方式二:properties配置文件方式
写一个属性文件:profile.properties
spring.profiles.active = prod
1
把资源文件导入进容器,然后启动即可
@PropertySource("classpath:profile.properties")
@Configuration
public class RootConfig { ... }

public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class);

Environment environment = context.getEnvironment();
        String[] activeProfiles = environment.getActiveProfiles();
        String[] defaultProfiles = environment.getDefaultProfiles();

System.out.println(ArrayUtils.toString(activeProfiles)); //{}
        System.out.println(ArrayUtils.toString(defaultProfiles)); //{default}
        System.out.println(context.getBean("person"));

}

结果输出:
{prod}
{default}
Person{name='我代表prod数据源', age=88}
123
注意:下面集中方式是SpringBoot环境下可直接使用的方式(Spring环境下默认不支持):
方式三:多profile文件方式
类似这么来写:

application-dev.properties
application-prod.properties

当然:类似的写多个yml也是可以的,此处就不作为一种新方式了

方式四:yml文件多文档块方式

方式五:命令行参数方式(重要)
形如:

方式六:JVM参数方式
形如:

思考题:若上面6中方式都有(SpringBoot环境下),或者只配置某几种方式,它们的优先级、最终谁会生效你知道吗?
这个问题各位小伙伴可自行思考,此处我给出两点提示:

API调用方式的优先第一考虑
各式各样的PropertySource属性源的优先级才是应该考虑的重中之重

spring中的Enviroment和profile到底怎么用相关推荐

  1. Spring中BeanPostProcessors后置处理器到底在哪里拦截

    研究spring源码的时候,发现注入bean到spring对象中有很多种,有一种是@bean注解,并且括号里可以写一些初始化时要执行的方法,还有销毁时执行的方法,spring中后置处理器可以将某些be ...

  2. Spring中的Ioc、DI到底是啥

    我们都知道,Spring是一款轻量级的IOC框架,Spring的核心就是Ioc和DI,并通过俩者解耦. 那么,Ioc.DI呢? Ioc 控制反转 Ioc(Inversion of control)控制 ...

  3. spring中是如何解析@Profile注解的

    profile注解定义为 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Do ...

  4. SpringBoot - Spring Boot 中的配置体系Profile全面解读

    文章目录 Pre Spring Boot 中的配置体系 配置文件与 Profile 主 application.properties 中指定激活的Profile Profile 配置信息只保存在一个文 ...

  5. spring中的事务到底是什么

    spring事务 事务在代码里或者数据库中都可以配置. 其含义理解为 一系列的数据操作,要么全部执行完成.要么都不执行.归纳为 1.原子性:事务是一个原子操作,由一系列动作组成.事务的原子性确保动作要 ...

  6. 【Java基础】Spring 中 Bean 的理解与使用

    大白话讲解: 从广义上 Spring 注解可以分为两类: 一类注解是用于注册 Bean 假如 IoC 容器是一间空屋子,首先这间空屋子啥都没有,我们要吃大餐,我们就要从外部搬运食材和餐具进来.这里把某 ...

  7. 【spring 5】AOP:spring中对于AOP的的实现

    在前两篇博客中,介绍了AOP实现的基础:静态代理和动态代理,这篇博客介绍spring中AOP的实现. 一.采用Annotation方式 首先引入jar包:aspectjrt.jar && ...

  8. Spring中WebApplicationContext

    ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用"容器"来表述它更容易理解一 些,ApplicationContext则是& ...

  9. 一起来踩踩 Spring 中这个循环依赖的坑!

    作者:Mythsman blog.mythsman.com/post/5d838c7c2db8a452e9b7082c/ 1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什 ...

最新文章

  1. 简易数字频率计(verilog HDL设计)(2020维护版本)
  2. c++11编码规范 NULL还是nullptr
  3. 完数c++语言程序_C语言经典100题(19)
  4. 如何看屈曲因子_Abaqus 非线性屈曲分析方法
  5. spring boot报FileSizeLimitExceededException异常的解决方法
  6. sd卡测速工具_怎样恢复SD卡数据?教你两招轻松恢复
  7. python实现多智能体一致性_多智能体深度学习算法MADDPG的PARL实践
  8. 预产期在线计算机,预产期计算器
  9. centos本地yum源配置
  10. 手工清除Windows服务器上的Steam挖矿病毒:HackTool/CoinMiner.a及Trojan/Miner.ac
  11. XTUOJ-1104-素数个数
  12. Instead Of 触发器
  13. 怎么用class引入svg_让动效更酷炫!4 个常见且常用的 SVG 交互动画方法
  14. 正则匹配所有的a标签
  15. 10本Linux PDF 书籍免费分享
  16. 索引的基本概念及作用
  17. C4D和3DMAX有什么区别?选哪个好?
  18. 迪威视讯打造全国网格化管理样板 龙华“织网工程”
  19. 互联网+时代,是更加开放还是封闭
  20. ECharts 饼图将标签设置在视觉引导线上方

热门文章

  1. 中超风云2服务器维护,《中超风云2》最详细的转生指南 快速提升战力
  2. Jenkins-工作区清理插件
  3. ATI 新动作:Linux 驱动及新催化剂驱动
  4. 淘宝运营 智钻精准营销 智钻分析 应用场景 投放方案
  5. 巨头新贵为何都爱「低代码」?一头数字化现金牛!
  6. Python基础篇(六)-- 类和对象
  7. 整除光棍 分数 20作者 翁恺单位 浙江大学
  8. vs无法引用项目问题
  9. Mysql 8.0 ERROR 1064 (42000)
  10. 利用C#编写网页投票器程序