Spring Environment(二)源码分析

2022-12-19,,,

Spring Environment(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring Environment 属性配置管理系列文章:

    Spring Environment(一)API 介绍
    Spring Environment(二)源码分析
    Spring Environment(三)生命周期

一、Environment 接口

public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles(); // @since 5.1 废弃,改用 Profiles(Profiles.of("dev"))
@Deprecated
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}

Environment 是对 JDK 环境、Servlet 环境的抽象,可以获取剖面相关的信息。同时实现了 PropertyResolver 接口用于解析属性占位符和类型转换,实际上是委托给 PropertySourcesPropertyResolver 完成的。

在其子类 ConfigurableEnvironment 接口中还提供了设置剖面和相关数据的 API。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// 1. 设置剖面
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles); // 2. 属性源
MutablePropertySources getPropertySources();
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment(); // 3. 合并两个环境信息
void merge(ConfigurableEnvironment parent);
}

二、Environment 默认实现类

Environment 的主要几个实现如下所示:

MockEnvironment:模拟的环境,用于测试时使用;
StandardEnvironment:标准环境,普通 Java 应用时使用,会自动注册 System.getProperties() 和 System.getenv()到环境;
StandardServletEnvironment:标准 Servlet 环境,其继承了 StandardEnvironment,Web 应用时使用,除了 StandardEnvironment 外,会自动注册 ServletConfig(DispatcherServlet)、ServletContext 及 JNDI 实例到环境;

StandardEnvironment 标准的 JDK 环境实现如下,将 OS 和 JVM 相关的配置信息都交由 StandardEnvironment 管理。

2.1 StandardEnvironment

public class StandardEnvironment extends AbstractEnvironment {
/** 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"; @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()));
}
}

2.2 StandardServletEnvironment

StandardServletEnvironment 除了注册系统变量外,还会自动注册 ServletConfig(DispatcherServlet)、ServletContext 及 JNDI 实例到环境;

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
/** Servlet context init parameters property source name: {@value}. */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** Servlet config init parameters property source name: {@value}. */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
/** JNDI property source name: {@value}. */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
} // 调用 initPropertySources 替换对应的占位符
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

其中 customizePropertySources 方法是在其父类 AbstractEnvironment 的构造方法中调用的,此时 ServletConfig 和 ServletContext 相关的信息还未初始化,因此需要一个占位符,在初始化 WEB 时自动替换。

public static void initServletPropertySources(MutablePropertySources sources,
@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) { Assert.notNull(sources, "'propertySources' must not be null");
String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletContextPropertySource(name, servletContext));
}
name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
}
}

三、AbstractEnvironment

Environment 的源码非常简单,就不多说了。

3.1 属性定义

(1) 常量

// 为 true 时 getSystemEnvironment() 返回空集合,屏蔽 OS 相关信息
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";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";

(2) 属性

// 剖面信息,查找时先从 activeProfiles 再从 defaultProfiles 中查找
private final Set<String> activeProfiles = new LinkedHashSet<>();
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles()); // 属性源
private final MutablePropertySources propertySources = new MutablePropertySources();
// propertyResolver 用于解析属性占位符和类型转换
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);

3.2 剖面相关的 API

public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
for (String profile : profiles) {
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
} else if (isProfileActive(profile)) {
return true;
}
}
return false;
} protected boolean isProfileActive(String profile) {
// 只要 profile 不为空且不以 ! 开头
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}

获取剖面信息如下:

@Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
} // 获取 activeProfiles,defaultProfiles 类似
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;
}
}

3.3 获取属性

private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources); @Override
public String getProperty(String key) {
return this.propertyResolver.getProperty(key);
}

获取属性都委托给 propertyResolver 完成了,propertyResolver 持有 propertySources 数据源,可以解析占位符和进行类型转换。

PropertyResolver 的使用参考:https://www.cnblogs.com/binarylei/p/10284826.html


每天用心记录一点点。内容也许不重要,但习惯很重要!

Spring Environment(二)源码分析的相关教程结束。

《Spring Environment(二)源码分析.doc》

下载本文的Word格式文档,以方便收藏与打印。