前言
在SpringBoot提供得众多特性中,自动配置无疑是对提升开发体验最显著的一个特性,SpringBoot基于这一特性为开发人员自动声明了若干开箱即用、具备某一功能的Bean。大多数情况下,自动配置得Bean刚好能满足大家的需求,但在某些情况下,不得不完整地覆盖它们,这个时候只需要重新声明相关类型的Bean即可,因为绝大多数自动配置的Bean都会由@ConditionalOnMissingBean注解修饰。幸运的是,如果只是想微调一些细节,比如改改端口号(server.port)和数据源URL(spring.datasource.url),那压根没必要重新声明ServerProperties和DataSourceProperties这俩Bean来覆盖自动配置的Bean。SpringBoot为自动配置的Bean提供了1000多个用于微调的属性,当需要调整设置时,只需要在环境变量、命令行或配置文件(application.properties/application.yml)中进行指定即可,这就是SpringBoot的配置外化特性。
当然,外部配置源并不局限于环境变量、命令行参数和配置文件这三种,感兴趣的读者可以自行阅读SpringBoot官方文档。在Spring中,BeanFactory扮演者Bean容器的角色,而Environment同样定位即为一个容器,即外部配置源中的属性都会被添加到Environment中。在微服务大行其道的今天,外部配置源又衍生了Disconf、Apollo和Nacos等分布式配置中心,但在Spring的地盘,还是要入乡随俗,从配置中心中读取到的属性依然会被追加到Environment中。
认识Environment
在实际工作中,我们与Environment打交道的机会并不多;如果业务Bean确实需要获取外部配置源中的某一属性,可以手动将Environment注入到该业务Bean中,也可以直接实现EnvironmentAware接口,得到Environment类型的Bean实例之后可以通过getProperty()获取具体属性值。Environment接口内容如下所示:
public interface Environment extends PropertyResolver {
/**
* Return the set of profiles explicitly made active for this environment. Profiles
* are used for creating logical groupings of bean definitions to be registered
* conditionally, for example based on deployment environment. Profiles can be
* activated by setting {@linkplain AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
* "spring.profiles.active"} as a system property or by calling
* {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
* <p>If no profiles have explicitly been specified as active, then any
* {@linkplain #getDefaultProfiles() default profiles} will automatically be activated.
* @see #getDefaultProfiles
* @see ConfigurableEnvironment#setActiveProfiles
* @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
*/
String[] getActiveProfiles();
/**
* Return the set of profiles to be active by default when no active profiles have
* been set explicitly.
* @see #getActiveProfiles
* @see ConfigurableEnvironment#setDefaultProfiles
* @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
*/
String[] getDefaultProfiles();
/**
* Return whether one or more of the given profiles is active or, in the case of no
* explicit active profiles, whether one or more of the given profiles is included in
* the set of default profiles. If a profile begins with '!' the logic is inverted,
* i.e. the method will return {@code true} if the given profile is <em>not</em> active.
* For example, {@code env.acceptsProfiles("p1", "!p2")} will return {@code true} if
* profile 'p1' is active or 'p2' is not active.
* @throws IllegalArgumentException if called with zero arguments
* or if any profile is {@code null}, empty, or whitespace only
* @see #getActiveProfiles
* @see #getDefaultProfiles
* @see #acceptsProfiles(Profiles)
* @deprecated as of 5.1 in favor of {@link #acceptsProfiles(Profiles)}
*/
@Deprecated
boolean acceptsProfiles(String... profiles);
/**
* Return whether the {@linkplain #getActiveProfiles() active profiles}
* match the given {@link Profiles} predicate.
*/
boolean acceptsProfiles(Profiles profiles);
}
public interface PropertyResolver {
/**
* Return whether the given property key is available for resolution,
* i.e. if the value for the given key is not {@code null}.
*/
boolean containsProperty(String key);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @see #getProperty(String, String)
* @see #getProperty(String, Class)
* @see #getRequiredProperty(String)
*/
@Nullable
String getProperty(String key);
/**
* Return the property value associated with the given key, or
* {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param defaultValue the default value to return if no value is found
* @see #getRequiredProperty(String)
* @see #getProperty(String, Class)
*/
String getProperty(String key, String defaultValue);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @see #getRequiredProperty(String, Class)
*/
@Nullable
<T> T getProperty(String key, Class<T> targetType);
/**
* Return the property value associated with the given key,
* or {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @param defaultValue the default value to return if no value is found
* @see #getRequiredProperty(String, Class)
*/
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* Return the property value associated with the given key (never {@code null}).
* @throws IllegalStateException if the key cannot be resolved
* @see #getRequiredProperty(String, Class)
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* Return the property value associated with the given key, converted to the given
* targetType (never {@code null}).
* @throws IllegalStateException if the given key cannot be resolved
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value are ignored and passed through unchanged.
* @param text the String to resolve
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* @see #resolveRequiredPlaceholders
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String)
*/
String resolvePlaceholders(String text);
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value will cause an IllegalArgumentException to be thrown.
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* or if any placeholders are unresolvable
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean)
*/
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
外部配置源中的属性并不是以单个属性为维度被添加到Environment中的,而是以PropertySource为维度。PropertySource是以属性源名称和该属性源中一组属性的抽象,MapPropertySource是一种最简单的实现,它通过Map<String, Object>来承载相关的属性。PropertySource内容如下:
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
/**
* Create a new {@code PropertySource} with the given name and source object.
*/
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
/**
* Create a new {@code PropertySource} with the given name and with a new
* {@code Object} instance as the underlying source.
* <p>Often useful in testing scenarios when creating anonymous implementations
* that never query an actual source but rather return hard-coded values.
*/
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
.......
}
从上述PropertySource内容来看,PropertySource自身是具备根据属性名获取属性值这一能力的
Environment应用
1、编写配置类
@Configuration
@RequiredArgsConstructor
public class PropertiesConfig {
private final Environment environment;
@PostConstruct
public void setProperties() {
PropertiesUtil.setEnvironment(environment);
}
}
2、编写工具
public class PropertiesUtil {
private static Environment env;
public static void setEnvironment(Environment env) {
PropertiesUtil.env = env;
}
// 获取application.yml中的配置
public static String getProperty(String key) {
return PropertiesUtil.env.getProperty(key);
}
}
3、使用
String nucleicDeptId = PropertiesUtil.getProperty("nucleicAci.deptId");