如何理解 Environment?
Environment 由 Spring 3.1 版本提出,表示当前应用的运行时环境。用于管理 Spring 中的条件配置 Profiles 和配置属性源。
Environment 的使用场景
Spring Boot 中,spring.profiles.active 属性可以指定激活的 profiles ,spring.profiles.default 属性可以指定无激活的 profiles 时使用的默认 profiles。属性可以配置在不同的数据源中,如配置文件,启动命令参数。
场景1:获取 Profiles
直接获取 Profiles 信息,不同运行环境激活不同的 Profiles,执行不同的逻辑。示例代码如下。
if(environment.acceptsProfiles("dev")){ doSomething(); }else{ doOtherSomething(); }
通过 @Profile 注解在不同环境注册不同的 bean。
@Profile 注解可以标注在表示组件的类或方法上,@Profile 注解 value 属性值和 Environment 中接受的 Profiles 值匹配时该组件才会被注册为 Spring 中的 bean。如下示例,可以在开发环境和生产环境使用不同的数据源。
@Configuration public class SpringConfiguration { @Bean @Profile("dev") public DataSource devDataSource(){ return new SimpleDataSource(); } @Bean @Profile("prod") public DataSource prodDataSource(){ return new SimpleDataSource(); } }
场景2:获取属性源中的属性
通过 Environment#getProperty(String) 获取
通过 @Value 注解注入属性值到 bean 的成员变量中。
如何获取 Environment ?
ApplicationContext#getEnvironment() 获取
可以通过 @Autowire 或实现 ApplicationContextAware 先获取 ApplicationContext。
@Autowire 注入、实现 EnvironmentAware 在 bean 生命周期中获取
Environment Profiles 管理
理解 Profiles
Profiles 表示条件配置,可用于在不同的环境中配置不同的 bean。Environment 对 Profile 管理的方法定义如下。
public interface Environment extends PropertyResolver { /** * 获取激活的 profiles */ String[] getActiveProfiles(); /** * 获取默认的 profiles */ String[] getDefaultProfiles(); /** * 给定的 profiles 是否被激活 */ @Deprecated boolean acceptsProfiles(String... profiles); /** * 给定的 profiles 是否被激活 */ boolean acceptsProfiles(Profiles profiles); }
Environment 接口中只有获取和匹配的方法,对 profiles 设置的方法则由其子接口 ConfigurableEnvironment 提供。ConfigurableEnvironment 对 profiles 设置的方法如下。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver { /** * 设置激活的 profiles */ void setActiveProfiles(String... profiles); /** * 添加激活的 profiles */ void addActiveProfile(String profile); /** * 设置默认的 profiles */ void setDefaultProfiles(String... profiles); }
@Profile 注解
伴随着 Environment,Spring 在 3.1 提出了一个 @Profile 注解,标注了该注解的 Component,只有指定的值满足 Environment 接受的 Profiles,Component 才会注册为 Spring 的 bean。使用方式可参见前面的示例,这里不再举例。
Environment 配置属性源管理
配置属性获取
Environment 接口并未定义有关配置属性源的方法,配置属性相关的方法由其父接口 PropertyResolver 定义。PropertyResolver 相关方法如下。
public interface PropertyResolver { /** * 是否存在给定名称的属性 */ boolean containsProperty(String key); /** * 获取给定名称属性源的值 */ @Nullable String getProperty(String key); /** * 获取给定名称的属性,不存在时则使用默认值 */ String getProperty(String key, String defaultValue); /** * 获取给定名称和期望类型的属性值 */ @Nullable <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; }
可以看到,PropertyResolver 接口中也只有属性的获取和匹配的方法,另外还提供了解析文本中属性占位符的方法。
配置属性源
理解配置属性源
属性可能来源不同的位置,如操作系统环境变量、启动参数、配置文件等。因此Environment 中具有不同的属性源。对属性源的获取相关方法则定义在 Environment 的子接口 ConfigurableEnvironment 中,相关方法定义如下。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver { /** * 获取可以修改的 PropertySources */ MutablePropertySources getPropertySources(); /** * 获取 System#getProperties() 的值,Environment 实现已经将该值作为默认的 PropertySource,建议不要直接调用该方法 */ Map<String, Object> getSystemProperties(); /** * 获取 System#getenv() 的值,Environment 实现已经将该值作为默认的 PropertySource,建议不要直接调用该方法 */ Map<String, Object> getSystemEnvironment(); }
ConfigurableEnvironment 中遇到一个新的类 MutablePropertySources,这个类对所有的属性源进行管理,可以方便的添加删除替换属性源,并且可以指定属性源的添加位置,这样前面的属性源就会具有较高的优先级。
先看属性源 PropertySource 的定义,核心代码如下。
public abstract class PropertySource<T> { /** * 属性源名称 */ protected final String name; /** * 底层的属性源 */ protected final T source; public PropertySource(String name, T source) { ... 省略部分代码 this.name = name; this.source = source; } @SuppressWarnings("unchecked") public PropertySource(String name) { this(name, (T) new Object()); } /** * 获取当前属性源名称 */ public String getName() { return this.name; } /** * 获取属性源存储属性的底层类 */ public T getSource() { return this.source; } /** * 属性源是否包含给定的属性名称 */ public boolean containsProperty(String name) { return (getProperty(name) != null); } /** * 获取属性名称关联的值 */ @Nullable public abstract Object getProperty(String name); }
可以认为,ProperySource 就表示某一个属性源,每个属性源都有一个名字,具体属性源的存储方式由子类指定,根据不同来源有所不同。常见的 PropertySource 如下。
ServletConfigPropertySource:Servlet 属性源
ServletContextPropertySource:ServletContext 属性源
CommandLinePropertySource:命令行属性源
SystemEnvironmentPropertySource:系统环境变量属性源
PropertiesPropertySource:Properties 属性源
ProperySources 是一个表示多个 PropertySource 的 Iterable,定义如下。
public interface PropertySources extends Iterable<PropertySource<?>> { /** * 是否存在给定名称的属性源 */ boolean contains(String name); /** * 获取给定名称的属性源 */ @Nullable PropertySource<?> get(String name); }
MutablePropertySources 对 PropertySources 进行实现,添加了对 PropertySource 的添加、删除、替换方法。底层使用一个 List 来保存 PropertySource,这样不同的属性源就有了优先级,排在前面的具有较高的优先级。部分常用的方法如下。
\
public class MutablePropertySources implements PropertySources { // 使用 List 存储属性源 private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); // 是否存在给定名称的属性源 @Override public boolean contains(String name) { return this.propertySourceList.contains(PropertySource.named(name)); } // 获取给定名称的属性源 @Override @Nullable public PropertySource<?> get(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.get(index) : null); } // 添加属性源到第一个位置,添加的属性源具有最高的优先级 public void addFirst(PropertySource<?> propertySource) { removeIfPresent(propertySource); this.propertySourceList.add(0, propertySource); } // 添加属性源到最后一个位置,添加的属性源就有最低的优先级 public void addLast(PropertySource<?> propertySource) { removeIfPresent(propertySource); this.propertySourceList.add(propertySource); } // 移除给定名称的属性源 @Nullable public PropertySource<?> remove(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.remove(index) : null); } // 替换给定名称的属性源为新的属性源 public void replace(String name, PropertySource<?> propertySource) { int index = assertPresentAndGetIndex(name); this.propertySourceList.set(index, propertySource); } }
@PropertySource 注解
为了支持自定义的配置文件作为属性源,Spring 在 3.1 版本中同样添加了注解 @ProertySource ,使用方式为在配置类上添加该注解,然后指定配置文件的路径。示例代码如下。
@PropertySource(name = "default", value = "classpath:/META-INF/default.properties", encoding = "UTF-8", ignoreResourceNotFound = true, factory = PropertySourceFactory.class) @Configuration public class Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class); Object source = context.getEnvironment().getPropertySources().get("default").getSource(); System.out.println(source); } }
打印结果如下。
{name=大鹏}
@PropertySource 各属性含义如下。
name:属性源的名称,如果不指定则会自动生成。
value:指定资源的路径。
encoding :表明资源文件的编码方式,如果不指定存在中文时可能会乱码。
ignoreResourceNotFound :找不到资源时是否忽略,默认为 false,找不到资源将抛出异常。
factory:创建 PropertySource 的工厂,默认的实现是 DefaultPropertySourceFactory 。