Spring 中的 Environment 、Profile 与 PropertySource

如何理解 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 。

Environment 的底层实现

Environment 接口的实现类

先来看 Environment 相关类图,对整体有一个了解。
Spring Environment 类图PropertyResolver、Environment、ConfigurableEnvironment 在前面都有提到,剩下比较重要的类如下。

  • ConfigurablePropertyResolver:PropertyResolver 的子接口,在PropertyResolver 的基础上添加了设置类型转换服务 ConfigurableConversionService 及占位符前缀、后缀等方法。
  • AbstractEnvironment:Environment 的抽象实现,并且提供了自定义默认属性源的方法,下文将详细对其进行分析。
  • StandardEnvironment:非 web 环境下的标准实现,默认添加了表示系统属性的 PropertiesPropertySource 和 操作系统环境变量的 SystemEnvironmentPropertySource。
  • StandardServletEnvironment: web 环境下的标准实现,默认添加了 ServletConfigPropertySource 和 ServletContextPropertySource 两种属性源。

Profiles 管理实现

Environment Profiles 相关方法由 AbstractEnvironment 进行实现。部分代码如下。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    
    
	// active profiles 属性名
	public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
	// default profiles 属性名
	public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

	// active profiles 集合
	private final Set<String> activeProfiles = new LinkedHashSet<>();
	// default profiles 集合
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
	
	// 当前 Environment 接受的 profiles
	@Override
	public boolean acceptsProfiles(Profiles profiles) {
    
    
		Assert.notNull(profiles, "Profiles must not be null");
		return profiles.matches(this::isProfileActive);
	}
	// 给定名称的 profile 是否激活
	protected boolean isProfileActive(String profile) {
    
    
		validateProfile(profile);
		Set<String> currentActiveProfiles = doGetActiveProfiles();
		// 激活的 profiles 或默认的 profiles 中存在给定名称的 profile 则表示接受
		return (currentActiveProfiles.contains(profile) ||
				(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
	}

	//获取激活的 profiles
	protected Set<String> doGetActiveProfiles() {
    
    
		synchronized (this.activeProfiles) {
    
    
			if (this.activeProfiles.isEmpty()) {
    
    
				// 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
				String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
    
    
					setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.activeProfiles;
		}
	}
	// 获取默认的 profiles
	protected Set<String> doGetDefaultProfiles() {
    
    
		synchronized (this.defaultProfiles) {
    
    
			if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
    
    
				// 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
				String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
    
    
					setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.defaultProfiles;
		}
	}
}

可以看到,Profiles 的数据来源主要包括两块,通过 ConfigurableEnvironment API 设置的 profiles 优先,如果未设置则会从配置属性源中获取,然后进行缓存。

@Profile 注解的实现

Spring 4.0 之后对 @Profile 进行了重构,使用 @Conditional 实现,参见 Spring 条件注解 @Conditional 使用及其底层实现。@Profile 源码如下。

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    
    

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();

}

可以看到,这里使用的条件是 ProfileCondition,源码如下。

class ProfileCondition implements Condition {
    
    

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
    
    
			for (Object value : attrs.get("value")) {
    
    
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
    
    
					return true;
				}
			}
			return false;
		}
		return true;
	}

}

ProfileCondition 实现较为简单,如果 Environment 接受 @Profile 的值,则允许组件进行注入。

Environment 属性获取实现

以 AbstractEnvironment#getProperty(String) 获取属性的方法为例,代码如下。

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

	@Override
	@Nullable
	public String getProperty(String key) {
    
    
		return this.propertyResolver.getProperty(key);
	}
}

属性源由 MutablePropertySources 进行保存,而属性的获取则委托为 PropertySourcesPropertyResolver,这是 Spring 中常用的一种设计模式,类实现某一个接口,而实现委托给接口的另一个实现完成。再看 PropertySourcesPropertyResolver 源码。

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
    
    
	// 属性源保存
	@Nullable
	private final PropertySources propertySources;

	// 构造方法
	public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
    
    
		this.propertySources = propertySources;
	}

	// 设置类型转换服务
	@Override
	public void setConversionService(ConfigurableConversionService conversionService) {
    
    
		this.propertyResolver.setConversionService(conversionService);
	}

	//设置占位符前缀
	@Override
	public void setPlaceholderPrefix(String placeholderPrefix) {
    
    
		this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
	}

	// 设置占位符后缀
	@Override
	public void setPlaceholderSuffix(String placeholderSuffix) {
    
    
		this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
	}
		
	// 获取属性值
	@Override
	@Nullable
	public String getProperty(String key) {
    
    
		return getProperty(key, String.class, true);
	}
	
	// 获取给定类型的属性值
	@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;
	}

}

PropertySourcesPropertyResolver 属性解析器循环属性源获取属性,有必要的情况下会解析属性值中的占位符,最后还会进行类型转换。类型转换为委托给 ConversionService,而占位符则有前缀、后缀。这些信息都在 AbstractEnvironment 中进行设置。

@ProperySource 注解的实现

@ProperySource 用来标注在配置类上,因此 @ProperySource 的处理在配置类的解析过程中。具体代码位置在ConfigurationClassParser#processPropertySource,这里简单进行分析。

	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    
    
		// 先获取 @PropertySource 注解的属性值
		String name = propertySource.getString("name");
		if (!StringUtils.hasLength(name)) {
    
    
			name = null;
		}
		String encoding = propertySource.getString("encoding");
		if (!StringUtils.hasLength(encoding)) {
    
    
			encoding = null;
		}
		String[] locations = propertySource.getStringArray("value");
		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

		Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
		// 默认使用 DefaultPropertySourceFactory 创建属性源的实例
		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

		for (String location : locations) {
    
    
			try {
    
    
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
				// 循环添加属性源
				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
			} catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
    
    
				// Placeholders not resolvable or resource not found when trying to open it
				if (ignoreResourceNotFound) {
    
    
					if (logger.isInfoEnabled()) {
    
    
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				} else {
    
    
					throw ex;
				}
			}
		}
	}

处理 @PropertySource 过程,循环将资源文件位置转换为 EncodedResource,然后创建属性源,最后将属性源添加到 Environment 中。如果同时指定的属性源的名称和多个属性源的位置,还会将多个名称相同的属性源组合为一个 PropertySource。这里看一眼 DefaultPropertySourceFactory 的实现。

public class DefaultPropertySourceFactory implements PropertySourceFactory {
    
    

	@Override
	public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
    
    
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}

}

默认创建的 PropertySource 是 ResourcePropertySource,这个属性源会从 properties 或 xml 中加载属性,由于使用了 EncodedResource ,因此可以加载到正确编码的内容而不会乱码。有关 Resource 的文章可见 Spring 资源管理 (Resource)

@Value 如何注入属性

Spring 中 @Autowire 和 @Value 注解的处理都使用 AutowiredAnnotationBeanPostProcessor ,它会在 bean 实例化后对使用 @Autowire、@Value、@Inject 等注解标注的属性赋值,底层实现依赖AutowireCapableBeanFactory#resolveDependency。这块由于比较复杂,后面会再开一篇文章对其分析,感兴趣的小伙伴可先自行阅读源码。

总结

本篇首先对 Environment 的使用进行介绍,然后对其底层的实现进行分析。理解 Environment 同样有助于熟悉 Spring 的第三方框架源码,欢迎大家留言交流,感谢。

猜你喜欢

转载自blog.csdn.net/zzuhkp/article/details/108415367