文章目录
- 一、理解 Spring Environment 抽象
- 1、源码初识
- 二、Environment 占位符处理
- 1、Spring 3.1 前占位符处理
- 2、Spring 3.1 + 占位符处理
- 3、代码实例
- 三、条件配置 Spring Profiles
- 1、Spring 4对@Profile的重构
- 四、依赖注入 Environment
- 1、依赖注入方式
- 直接依赖注入
- 间接依赖注入
- 2、代码实例
- 3、源码分析
- 五、依赖查找 Environment
- 1、依赖查找方式
- 直接依赖查找
- 间接依赖查找
- 2、代码实例
- 3、源码分析
- 六、依赖注入 @Value
- 1、源码分析
- 2、代码实例分析
- 3、类型转换
- 源码分析
- @Value中发生的类型转换
- 七、Spring 配置属性源 PropertySource
- 1、初始PropertySource
- 2、Spring 內建的配置属性源
- 3、基于注解扩展 Spring 配置属性源
- 实现原理
- 4、基于 API 扩展 Spring 配置属性源
- 代码实例
- 八、Spring 4.1 测试配置属性源 – @TestPropertySource
- 参考资料
一、理解 Spring Environment 抽象
统一的 Spring 配置属性管理:
Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)。
条件化 Spring Bean 装配管理:
通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。
Environment 接口使用场景:
• ⽤于属性占位符处理
• 用于转换 Spring 配置属性类型
• 用于存储 Spring 配置属性源(PropertySource)
• 用于 Profiles 状态的维护
1、源码初识
Environment接口表示当前应用程序运行的环境。虽然很简单,但是内部的实现其实是很复杂的。
Environment接口继承了PropertyResolver接口,PropertyResolver接口就是用于处理和解析属性配置的,例如resolvePlaceholders方法就是处理占位符的,还有一些对配置操作的方法。
public interface PropertyResolver {
boolean containsProperty(String key);
@Nullable
String getProperty(String key);
String getProperty(String key, String defaultValue);
@Nullable
T getProperty(String key, Class targetType);
T getProperty(String key, Class targetType, T defaultValue);
String getRequiredProperty(String key) throws IllegalStateException;
T getRequiredProperty(String key, Class targetType) throws IllegalStateException;
// 处理占位符
String resolvePlaceholders(String text);
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
Environment接口中还定义了关于profile相关的接口:
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles();
@Deprecated
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}
通常来说,我们使用Spring上下文通过getEnvironment方法获取的Environment,都是调用的ConfigurableApplicationContext的getEnvironment方法,该方法返回一个ConfigurableEnvironment,顾名思义,是一个可以配置写入的Environment。
ConfigurableEnvironment接口继承了Environment接口和ConfigurablePropertyResolver接口,
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
// 获取PropertySources
MutablePropertySources getPropertySources();
Map getSystemProperties();
Map getSystemEnvironment();
// Environment合并
void merge(ConfigurableEnvironment parent);
}
二、Environment 占位符处理
1、Spring 3.1 前占位符处理
• 组件:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
• 接口:org.springframework.util.StringValueResolver
PropertyPlaceholderConfigurer接口从Spring5.2开始就已经弃用了建议使用PropertySourcesPlaceholderConfigurer。
2、Spring 3.1 + 占位符处理
• 组件:org.springframework.context.support.PropertySourcesPlaceholderConfigurer
• 类:org.springframework.beans.factory.config.EmbeddedValueResolver
3、代码实例
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
public class EnvironmentPlaceholderDemo {
@Value("${user.id}")
private Long id;
@Value("${user.name}")
private String name;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(EnvironmentPlaceholderDemo.class);
// 启动 Spring 应用上下文
context.refresh();
EnvironmentPlaceholderDemo environmentPlaceholderDemo = context.getBean(EnvironmentPlaceholderDemo.class);
System.out.println(environmentPlaceholderDemo.id);
System.out.println(environmentPlaceholderDemo.name);
// 关闭 Spring 应用上下文
context.close();
}
/**
* Spring 3.1前使用PropertyPlaceholderConfigurer处理占位符
* 加上static保证提前初始化
*/
@Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setLocation(new ClassPathResource("META-INF/default.properties"));
propertyPlaceholderConfigurer.setFileEncoding("UTF-8");
return propertyPlaceholderConfigurer;
}
/**
* Spring 3.1 + 推荐使用PropertySourcesPlaceholderConfigurer
* 这里的user.name显示的并不是张三,而是Admin,这涉及外部化配置
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource("META-INF/default.properties"));
propertySourcesPlaceholderConfigurer.setFileEncoding("UTF-8");
return propertySourcesPlaceholderConfigurer;
}
}
分别使用以上两个Bean,效果是相同的。
三、条件配置 Spring Profiles
Spring 3.1 条件配置Profiles,也是基于Environment实现的。
API:org.springframework.core.env.ConfigurableEnvironment
• 修改:addActiveProfile(String)、setActiveProfiles(String…) 和 setDefaultProfiles(String…)
• 获取:getActiveProfiles() 和 getDefaultProfiles()
• 匹配:#acceptsProfiles(String…) 和 acceptsProfiles(Profiles)
注解:@org.springframework.context.annotation.Profile
关于Profile的使用,请移步:
Spring注解驱动原理及源码,深入理解Spring注解驱动
1、Spring 4对@Profile的重构
基于 Spring 4 org.springframework.context.annotation.Condition 接口实现
• org.springframework.context.annotation.ProfileCondition
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
Spring4使用了@Conditional(ProfileCondition.class)来重构了@Profile。
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 使用AnnotatedTypeMetadata 获取@Profile的属性
MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) { // value属性
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true; // 如果Environment中符合value属性之一,就true
}
}
return false;
}
return true;
}
}
四、依赖注入 Environment
1、依赖注入方式
直接依赖注入
• 通过 EnvironmentAware 接口回调
• 通过 @Autowired 注入 Environment
间接依赖注入
• 通过 ApplicationContextAware 接口回调
• 通过 @Autowired 注入 ApplicationContext
2、代码实例
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;
/**
* 依赖注入 {@link Environment} 示例
*/
public class InjectingEnvironmentDemo implements EnvironmentAware, ApplicationContextAware {
private Environment environment;
@Autowired
private Environment environment2;
private ApplicationContext applicationContext;
@Autowired
private ApplicationContext applicationContext2;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(InjectingEnvironmentDemo.class);
// 启动 Spring 应用上下文
context.refresh();
InjectingEnvironmentDemo injectingEnvironmentDemo = context.getBean(InjectingEnvironmentDemo.class);
System.out.println(injectingEnvironmentDemo.environment); // true
System.out.println(injectingEnvironmentDemo.environment == injectingEnvironmentDemo.environment2); // true
System.out.println(injectingEnvironmentDemo.environment == context.getEnvironment()); // true
System.out.println(injectingEnvironmentDemo.environment == injectingEnvironmentDemo.applicationContext.getEnvironment()); // true
System.out.println(injectingEnvironmentDemo.applicationContext == injectingEnvironmentDemo.applicationContext2); // true
System.out.println(injectingEnvironmentDemo.applicationContext == context); // true
// 关闭 Spring 应用上下文
context.close();
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
我们发现,Environment在上下文中只有一个,不管我们怎么获取,都是同一个对象,也就是单例对象。
3、源码分析
EnvironmentAware和ApplicationContextAware在Bean的生命周期的Aware回调阶段,会将Environment和ApplicationContext设置到Bean中:
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(下)
Environment在获取的时候,会进行判断:
// org.springframework.context.support.AbstractApplicationContext#getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
虽然该方法并不是线程安全的方法,但是ApplicationContext上下文中独一份,Environment也是上下文中独一份,在容器启动时会自动初始化Environment。
五、依赖查找 Environment
1、依赖查找方式
直接依赖查找
通过 org.springframework.context.ConfigurableApplicationContext#ENVIRONMENT_BEAN_NAME
间接依赖查找
通过 org.springframework.context.ConfigurableApplicationContext#getEnvironment
2、代码实例
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import static org.springframework.context.ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME;
/**
* 依赖查找 {@link Environment} 示例
*/
public class LookupEnvironmentDemo implements EnvironmentAware {
private Environment environment;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(LookupEnvironmentDemo.class);
// 启动 Spring 应用上下文
context.refresh();
LookupEnvironmentDemo lookupEnvironmentDemo = context.getBean(LookupEnvironmentDemo.class);
// 通过 Environment Bean 名称 直接 依赖查找
Environment environment = context.getBean(ENVIRONMENT_BEAN_NAME, Environment.class);
ConfigurableEnvironment configurableEnvironment = context.getEnvironment();
System.out.println(lookupEnvironmentDemo.environment);
System.out.println(environment == configurableEnvironment); // true
System.out.println(lookupEnvironmentDemo.environment == environment); // true
// 关闭 Spring 应用上下文
context.close();
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
3、源码分析
EnvironmentAware和ApplicationContextAware在Bean的生命周期的Aware回调阶段,会将Environment和ApplicationContext设置到Bean中:
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(下)
ApplicationContextAwareProcessor接口的postProcessBeforeInitialization方法中,对Aware接口进行了回调
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction
我们发现,这里的setEnvironment,就是使用的applicationContext.getEnvironment()获取的Environment。
在SpringIOC容器启动时,执行的prepareBeanFactory中注册了Environment:
// org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// Register default environment beans.
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { // 注册Singleton的Environment
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
我们发现,这里注册Singleton的Environment也是调用了getEnvironment方法获取的Environment,而这个getEnvironment方法,正是Application中的方法:
// org.springframework.context.support.AbstractApplicationContext#getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
六、依赖注入 @Value
通过实现 – org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor 注入 @Value。
1、源码分析
假如说@Value标注在一个字段上时,会执行AutowiredFieldElement内部类的inject方法:
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set autowiredBeanNames = new LinkedHashSet(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
// 关键方法,和Bean的处理是一样的,其中调用了doResolveDependency方法
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
DefaultListableBeanFactory的doResolveDependency中有一段关键逻辑getSuggestedValue获取值。
// org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
// 类型转换
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
// ...略
}
2、代码实例分析
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* {@link Value @Value} 注解示例
*/
public class ValueAnnotationDemo {
@Value("${user.name}")
private String userName;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(ValueAnnotationDemo.class);
// 启动 Spring 应用上下文
context.refresh();
ValueAnnotationDemo valueAnnotationDemo = context.getBean(ValueAnnotationDemo.class);
System.out.println(valueAnnotationDemo.userName); // "Admin" 当前计算机用户
// 关闭 Spring 应用上下文
context.close();
}
}
运行期间,我们看一下debug:
获取到数据之后,进行填充。
后续还有类型转换的逻辑。
3、类型转换
关于Spring的类型转换请移步:
Spring 类型转换详解,SpringBean创建时属性类型转换源码详解
Environment 底层实现:
- 底层实现 – org.springframework.core.env.PropertySourcesPropertyResolver 核心方法 – convertValueIfNecessary(Object,Class)
- 底层服务 – org.springframework.core.convert.ConversionService默认实现 – org.springframework.core.convert.support.DefaultConversionService
源码分析
Environment接口有一个StandardEnvironment标准实现,StandardEnvironment又分StandardServletEnvironment和StandardReactiveWebEnvironment(Springboot)。
StandardEnvironment中获取的Property是在其父类实现的,而AbstractEnvironment的getProperty还是委托给propertyResolver实现的
// org.springframework.core.env.AbstractEnvironment#getProperty(java.lang.String, java.lang.Class)
@Override
@Nullable
public T getProperty(String key, Class targetType) {
return this.propertyResolver.getProperty(key, targetType);
}
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
PropertySourcesPropertyResolver的父类是AbstractPropertyResolver,提供了类型转换的方法convertValueIfNecessary:
// org.springframework.core.env.AbstractPropertyResolver#convertValueIfNecessary
@Nullable
protected T convertValueIfNecessary(Object value, @Nullable Class targetType) {
if (targetType == null) {
return (T) value;
}
ConversionService conversionServiceToUse = this.conversionService;
if (conversionServiceToUse == null) {
// Avoid initialization of shared DefaultConversionService if
// no standard type conversion is needed in the first place...
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
conversionServiceToUse = DefaultConversionService.getSharedInstance();
}
return conversionServiceToUse.convert(value, targetType);
}
此处的类型转换,就是使用ConfigurableConversionService进行转换的。
@Value中发生的类型转换
底层实现 – org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
上面我们分析@Value时,在doResolveDependency中获取了注入的值,接下来就是发生了类型转换:
// org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
// 类型转换
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
// ...略
}
底层服务 – org.springframework.beans.TypeConverter 默认实现 – org.springframework.beans.TypeConverterDelegate
• java.beans.PropertyEditor
• org.springframework.core.convert.ConversionService
七、Spring 配置属性源 PropertySource
关于PropertySource在该文章中也有介绍:
Spring配置详解,Spring配置元信息详解,Spring配置大全及源码分析
1、初始PropertySource
API
• 单配置属性源 – org.springframework.core.env.PropertySource
• 多配置属性源 – org.springframework.core.env.PropertySources
注解
• 单配置属性源 – @org.springframework.context.annotation.PropertySource
• 多配置属性源 – @org.springframework.context.annotation.PropertySources
与Environment的关联
• 存储对象 – org.springframework.core.env.MutablePropertySources
• 关联方法 – org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
2、Spring 內建的配置属性源
內建 PropertySource:
PropertySource 类型 |
说明 |
org.springframework.core.env.CommandLinePropertySource |
命令行配置属性源 |
org.springframework.jndi.JndiPropertySource |
JDNI 配置属性源 |
org.springframework.core.env.PropertiesPropertySource |
Properties 配置属性源 |
org.springframework.web.context.support.ServletConfigPropertySource |
Servlet 配置属性源 |
org.springframework.web.context.support.ServletContextPropertySource |
ServletContext 配置属性源 |
org.springframework.core.env.SystemEnvironmentPropertySource |
环境变量配置属性源 |
… |
3、基于注解扩展 Spring 配置属性源
@org.springframework.context.annotation.PropertySource
4.3 新增语义
• 配置属性字符编码 – encoding
• 新增实现org.springframework.core.io.support.PropertySourceFactory
• 适配对象 – org.springframework.core.env.CompositePropertySource
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
实现原理
入口 – org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
org.springframework.context.annotation.ConfigurationClassParser#processPropertySource
// org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// org.springframework.context.annotation.ConfigurationClassParser#processPropertySource
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
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");
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);
// 创建Resource
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;
}
}
}
}
将Resource添加至应用。
// org.springframework.context.annotation.ConfigurationClassParser#addPropertySource
private void addPropertySource(PropertySource> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource> existing = propertySources.get(name);
if (existing != null) {
PropertySource> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}
4、基于 API 扩展 Spring 配置属性源
• Spring 应用上下文启动前装配 PropertySource
• Spring 应用上下文启动后装配 PropertySource
代码实例
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.*;
import java.util.HashMap;
import java.util.Map;
/**
* {@link Environment} 配置属性源变更示例
*/
public class EnvironmentPropertySourceChangeDemo {
@Value("${user.name}") // 不具备动态更新能力
private String userName;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(EnvironmentPropertySourceChangeDemo.class);
// 在 Spring 应用上下文启动前,调整 Environment 中的 PropertySource
ConfigurableEnvironment environment = context.getEnvironment();
// 获取 MutablePropertySources 对象
MutablePropertySources propertySources = environment.getPropertySources();
// 动态地插入 PropertySource 到 PropertySources 中
Map source = new HashMap();
source.put("user.name", "张三");
MapPropertySource propertySource = new MapPropertySource("first-property-source", source);
propertySources.addFirst(propertySource);
// 启动 Spring 应用上下文
context.refresh();
// 启动后覆盖属性,依赖注入的并不会同步修改
source.put("user.name", "007");
EnvironmentPropertySourceChangeDemo environmentPropertySourceChangeDemo = context.getBean(EnvironmentPropertySourceChangeDemo.class);
System.out.println(environmentPropertySourceChangeDemo.userName); // 张三
/**
* PropertySource(name=first-property-source) 'user.name' 属性:007
* PropertySource(name=systemProperties) 'user.name' 属性:Admin
* PropertySource(name=systemEnvironment) 'user.name' 属性:null
*/
for (PropertySource ps : propertySources) {
System.out.printf("PropertySource(name=%s) 'user.name' 属性:%sn", ps.getName(), ps.getProperty("user.name"));
}
// 关闭 Spring 应用上下文
context.close();
}
}
这里我们发现,在Spring容器启动前设置的PropertySource,是会应用到Bean的属性中的,启动后设置的属性并不会同步修改。
Springboot使用的十四种外部化配置的优先级,也是基于这个原理来实现的。
八、Spring 4.1 测试配置属性源 – @TestPropertySource
@TestPropertySource是比@PropertySource优先加载的(测试优先)。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
/**
* {@link TestPropertySource} 测试示例
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestPropertySourceTest.class) // Spring 注解驱动测试注解
@TestPropertySource(
properties = "user.name = 李四", // PropertySource(name=Inlined Test Properties)
locations = "classpath:/META-INF/test.properties"
)
public class TestPropertySourceTest {
@Value("${user.name}") // "mercyblitz" Java System Properties
private String userName;
@Autowired
private ConfigurableEnvironment environment;
@Test
public void testUserName() {
assertEquals("李四", userName);
for (PropertySource ps : environment.getPropertySources()) {
System.out.printf("PropertySource(name=%s) 'user.name' 属性:%sn", ps.getName(), ps.getProperty("user.name"));
}
}
}
参考资料
极客时间-《小马哥讲 Spring 核心编程思想》
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 【Azure 媒体服务】Media Service的编码示例 — 创建缩略图子画面的.NET代码调试问题
问题描述 在中国区Azure上,使用Media Service服务,想要使用.NET的代码来对上传视频创建缩略图(Thumbnail) 。 通过官网文档(https://docs.azure.cn/zh-cn/media-services/latest/sam…