优秀的编程知识分享平台

网站首页 > 技术文章 正文

Spring源码学习 | Environment多环境

nanyue 2024-08-09 07:03:08 技术文章 9 ℃

什么是多环境?

任何一个应用级框架都会存在多环境问题,何谓多环境?正常来说,我们在项目开发过程中会经历开发环境、测试环境、灰度环境、生产环境等,以数据库配置为例,不同环境的数据库连接配置是不一样的,程序启动时需要根据当前环境获取相应的配置,这就是多环境,同一个资源,在不同的环境下拥有不同的版本,需要程序根据当前环境获取到相应的版本资源。

@Profile 的用法

@Profile 注解可以声明当前 Bean 的适用环境,为一个接口的两个实现分别声明不同的 Profile

public class Config {

    public static interface DSConfig {
        void init();
    }

    @Profile("dev")
    @Component
    public static class DevDSConfig implements DSConfig {
        public void init() {
            System.out.println("init dev ds config");
        }
    }

    @Profile("prod")
    @Component
    public static class ProdDSConfig implements DSConfig {
        public void init() {
            System.out.println("init prod ds config");
        }
    }
}

通过 spring.profiles.active 参数设置当前容器的环境,也可以通过启动参数 -Dspring.profiles.active 的方式进行设置。

public class EnvironmentTest {

    public static void main(String[] args) {
        System.setProperty("spring.profiles.active", "dev");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan(EnvironmentTest.class.getPackage().getName());
        context.refresh();
        // 使用接口类型获取 Bean
        Config.DSConfig bean = context.getBean(Config.DSConfig.class);
        // 输出 init dev ds config
        bean.init();
    }

}

如果两个实现类上不加 @Profile 注解,程序会报 NoUniqueBeanDefinitionException 异常。

SpringBoot 多环境配置

devtest 两个不同的环境分别创建一份配置文件 application-dev.ymlapplication-test.yml

# application-dev.yml
profileName: dev

# application-test.yml
profileName: test

在配置文件 application.yml 中配置 spring.profiles.active 属性,设置当前环境

spring:
  profiles:
    active: dev

SpringBoot 项目中获取属性,可以看到在 SpringBoot 项目中会根据环境自动加载相应配置文件中的属性。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        // 输出 dev
        System.out.println(context.getEnvironment().getProperty("profileName"));
    }

}

Environment 源码解析

Spring 将环境抽象为 Environment 接口,主要处理两件事情:

  • 管理 Profile
  • 获取 Property,这部分功能依赖于 PropertySource 配置源,在其基础上增加了对占位符的处理以及类型转换

PropertySource 如何加载配置不懂的同学,可以查看 Spring源码学习 | PropertySource配置源加载

Environment 继承体系

Environment 接口继承自 PropertyResolver 接口,PropertyResolver 接口定义的是一个属性解析器,可以获取属性值,并对属性值进行类型转换,同时还定义了对占位符属性的支持。

public interface PropertyResolver {
	/**
	 * 是否包含某个属性
	 */
	boolean containsProperty(String key);
	/**
	 * 获取某个属性的值
	 */
	String getProperty(String key);
	/**
     * 获取某个属性的值,如果不存在,则设置为默认值
	 */
	String getProperty(String key, String defaultValue);
	/**
     * 获取某个属性的值,并转换为指定类型
	 */
	<T> T getProperty(String key, Class<T> targetType);
	/**
     * 获取某个属性的值,并转换为指定类型,如果不存在,则设置为默认值
	 */
	<T> T getProperty(String key, Class<T> targetType, T defaultValue);
	/**
	 * 获取某个属性的值
	 */
	String getRequiredProperty(String key) throws IllegalStateException;
	/**
     * 获取某个属性的值,并转换为指定类型
	 */
	<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
	/**
     * 解析占位符 ${...}
	 */
	String resolvePlaceholders(String text);
	/**
     * 解析占位符 ${...}
	 */
	String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

Environment 接口本身定义了 Profile 相关方法

public interface Environment extends PropertyResolver {
	/**
	 * 获取当前激活的 profile
	 */
	String[] getActiveProfiles();
	/**
	 * 获取默认的 profile
	 */
    String[] getDefaultProfiles();
	/**
     * 获取当前可用的 profile
	 */
	boolean acceptsProfiles(Profiles profiles);
}

ConfigurablePropertyResolver 接口对 PropertyResolver 接口进行了扩展,提供配置功能,支持设置 ConversionService(用于属性类型转换),另外提供了一些占位符相关的设置方法。

public interface ConfigurablePropertyResolver extends PropertyResolver {
	/**
	 * 获取 ConversionService,用于类型转换
	 */
	ConfigurableConversionService getConversionService();
	void setConversionService(ConfigurableConversionService conversionService);
    /**
     * 设置占位符开始字符
     */
	void setPlaceholderPrefix(String placeholderPrefix);
	/**
	 * 设置占位符结束字符
	 */
	void setPlaceholderSuffix(String placeholderSuffix);
	/**
	 * 设置占位符分隔符
	 */
	void setValueSeparator(@Nullable String valueSeparator);
	/**
	 * 在 getProperty 时是否忽略无法解析的占位符
	 */
	void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
	/**
	 * 设置必须的属性,用于 validateRequiredProperties 验证时使用
	 */
	void setRequiredProperties(String... requiredProperties);
	void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

ConfigurableEnvironment 接口继承了 EnvironmentConfigurablePropertyResolver

这是 Spring 的一贯设计手法,每个接口只提供一个最小的功能集合,然后逐步扩展,最后由一个统一的接口实现分离出来的全部接口,形成一个完整的功能体,然后为其提供一个抽象实现类,实现基本功能。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
	/**
	 * 设置当前激活的 profile
	 */
	void setActiveProfiles(String... profiles);
	/**
	 * 增加一个当前激活的 profile
	 */
	void addActiveProfile(String profile);
	/**
     * 设置默认的 profile
	 */
	void setDefaultProfiles(String... profiles);
	/**
     * 获取 PropertySource 配置源
	 */
	MutablePropertySources getPropertySources();
	/**
	 * 获取系统属性:System.getProperties()
	 */
	Map<String, Object> getSystemProperties();
	/**
     * 获取系统环境变量:System.getenv()
	 */
	Map<String, Object> getSystemEnvironment();
	/**
	 * 环境合并,将 activeProfiles、defaultProfiles 及 propertySources 进行合并
	 */
	void merge(ConfigurableEnvironment parent);
}

AbstractEnvironmentConfigurableEnvironment 接口的默认实现类,为以上接口设计的功能提供了默认实现。

首先看对 Profile 的管理,这部分的实现比较简单,根据代码中的注释就可以理解

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 static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
    // 当前激活的 profile 集合
    private final Set<String> activeProfiles = new LinkedHashSet<>();
    // 默认的 profile 集合
    private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
    
    protected Set<String> getReservedDefaultProfiles() {
        return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
    }
    
    public String[] getActiveProfiles() {
        return StringUtils.toStringArray(doGetActiveProfiles());
    }
    /**
     * 首先查看本地缓存 activeProfiles 中是否有值
     * 如果没有,则从配置文件中解析 spring.profiles.active 属性,并缓存至本地
     */
    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
    public void setActiveProfiles(String... profiles) {
        synchronized (this.activeProfiles) {
            this.activeProfiles.clear();
            for (String profile : profiles) {
                validateProfile(profile);
                this.activeProfiles.add(profile);
            }
        }
    }
    // 增加一个 activeProfile
    public void addActiveProfile(String profile) {
        validateProfile(profile);
        doGetActiveProfiles();
        synchronized (this.activeProfiles) {
            this.activeProfiles.add(profile);
        }
    }
    // 获取默认 profile
    public String[] getDefaultProfiles() {
        return StringUtils.toStringArray(doGetDefaultProfiles());
    }
    /**
     * 首先查看本地缓存 defaultProfiles 中是否有值
     * 如果没有,则从配置文件中解析 spring.profiles.default 属性,并缓存至本地
     * 如果没有,则使用默认的 default
     */
    protected Set<String> doGetDefaultProfiles() {
        synchronized (this.defaultProfiles) {
            if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
                String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.defaultProfiles;
        }
    }
}

然后看获取属性是如何实现的,AbstractEnvironment 定义了两个变量:

  • MutablePropertySources,配置源集合,负责加载属性,只需要向该集合中添加配置源即可
  • PropertySourcesPropertyResolver,属性解析器,支持占位符处理,支持属性类型转换

这里使用了一个巧妙的设计,AbstractEnvironment 初始化时会创建一个 MutablePropertySources 配置源集合,但此时该集合是空的,然后该集合又作为参数设置到了 PropertySourcesPropertyResolver 内部。AbstractEnvironment 在构造方法中调用了 customizePropertySources 方法,并传入了 propertySources,子类只需要实现 customizePropertySources 方法就可以往 propertySources 中添加配置源,而配置的读取、转换所有工作都在 PropertySourcesPropertyResolver 中进行实现。

PropertySource 如何加载配置不懂的同学,可以查看 Spring源码学习 | PropertySource配置源加载

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    // 配置源
	private final MutablePropertySources propertySources = new MutablePropertySources();
	// 属性解析
	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
    }
}

PropertySourcesPropertyResolver 的实现在后面会详细介绍,现在先看看 AbstractEnvironment 的实现类。AbstractEnvironment 的实现类,唯一需要做的事情就是向 MutablePropertySources 中添加需要的配置源,并且保持顺序。

StandardEnvironment

向配置源添加 System.getenv()System.getProperties() 两个配置源

public class StandardEnvironment extends AbstractEnvironment {
	// 系统变量,System.getenv()
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    // JAVA属性,入参变量,System.getProperties()
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
	
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
}

StandardServletEnvironment

向配置源添加 ServletConfigServletContext 配置源,这里设置的是 StubPropertySource 空配置源,只是为了占个位置,方便 ServletContext 启动完成后替换为真正的配置源。

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";

	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);
	}

	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}

}

PropertySourcesPropertyResolver 源码解析

AbstractPropertyResolver

AbstractPropertyResolverConfigurablePropertyResolver 接口的默认实现,AbstractEnvironment 也实现了 ConfigurablePropertyResolver 接口,但本身并没有实现功能,而是通过组合的方式委托给了 AbstractPropertyResolver 的实现类。

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
    // 数据类型转换,如果没有设置,则使用 DefaultConversionService
	private volatile ConfigurableConversionService conversionService;
	private PropertyPlaceholderHelper nonStrictHelper;
	private PropertyPlaceholderHelper strictHelper;
	private boolean ignoreUnresolvableNestedPlaceholders = false;
    // 占位符,默认格式为 ${...}
	private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
	private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
	// 分隔符,用于设置默认值 ${a:1}
	private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
	// 必须的属性,通过 validateRequiredProperties 方法检查属性必须存在
	private final Set<String> requiredProperties = new LinkedHashSet<>();

	// 不严格解析占位符
	public String resolvePlaceholders(String text) {
		if (this.nonStrictHelper == null) {
			this.nonStrictHelper = createPlaceholderHelper(true);
		}
		return doResolvePlaceholders(text, this.nonStrictHelper);
	}

    // 严格解析占位符,如果解析不了占位符,抛异常
	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		return doResolvePlaceholders(text, this.strictHelper);
	}
	/**
     * 解析占位符
     * 根据 ignoreUnresolvableNestedPlaceholders 选择是否严格解析
	 */
	protected String resolveNestedPlaceholders(String value) {
		if (value.isEmpty()) {
			return value;
		}
		return (this.ignoreUnresolvableNestedPlaceholders ?
				resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
	}
    // 创建占位符解析器
	private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
		return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
				this.valueSeparator, ignoreUnresolvablePlaceholders);
	}
    // 占位符的解析使用 PropertyPlaceholderHelper 进行处理
	private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}
	/**
	 * 属性类型转换
	 */
	protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
		if (targetType == null) {
			return (T) value;
		}
		ConversionService conversionServiceToUse = this.conversionService;
		if (conversionServiceToUse == null) {
			if (ClassUtils.isAssignableValue(targetType, value)) {
				return (T) value;
			}
			conversionServiceToUse = DefaultConversionService.getSharedInstance();
		}
		return conversionServiceToUse.convert(value, targetType);
	}
    // 获取原始值,不进行占位符解析
	protected abstract String getPropertyAsRawString(String key);
}

PropertySourcesPropertyResolver

PropertySourcesPropertyResolver 获取属性的过程,可以分为三步:

  • 第一步:轮询 PropertySources 获取属性
  • 第二步:通过 resolveNestedPlaceholders 方法,解析占位符,解析的过程由 PropertyPlaceholderHelper 类负责
  • 第三步:通过 conversionServiceToUse 对属性进行类型转换
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {

    // 数据源,设置为 MutablePropertySources 对象
	private final PropertySources propertySources;

	public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
		this.propertySources = propertySources;
	}

	public String getProperty(String key) {
		return getProperty(key, String.class, true);
	}

	public <T> T getProperty(String key, Class<T> targetValueType) {
		return getProperty(key, targetValueType, true);
	}

	protected String getPropertyAsRawString(String key) {
		return getProperty(key, String.class, false);
	}

	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);
					}
					// 类型转换
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		return null;
	}

}


最后关注一下,共同学习 Spring 源码

最近发表
标签列表