前言
在我刚入行不久时,总是对上下文(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次提起,此处略