一、理解 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> 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 {

	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<String, Object> getSystemProperties();

	Map<String, Object> 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<String, Object> 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<Object>() {
			@Override
			public Object run() {
				invokeAwareInterfaces(bean);
				return null;
			}
		}, acc);
	}
	else {
		invokeAwareInterfaces(bean);
	}

	return bean;
}

private void invokeAwareInterfaces(Object bean) {
	if (bean instanceof Aware) {
		if (bean instanceof EnvironmentAware) {
			((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
		}
		if (bean instanceof EmbeddedValueResolverAware) {
			((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
		}
		if (bean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
		}
		if (bean instanceof ApplicationEventPublisherAware) {
			((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
		}
		if (bean instanceof MessageSourceAware) {
			((MessageSourceAware) bean).setMessageSource(this.applicationContext);
		}
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}
}

我们发现,这里的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<String> 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<String> 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<T>)
@Override
@Nullable
public <T> T getProperty(String key, Class<T> 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> T convertValueIfNecessary(Object value, @Nullable Class<T> 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<String> 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.JndiPropertySourceJDNI 配置属性源
org.springframework.core.env.PropertiesPropertySourceProperties 配置属性源
org.springframework.web.context.support.ServletConfigPropertySourceServlet 配置属性源
org.springframework.web.context.support.ServletContextPropertySourceServletContext 配置属性源
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<String, Object> 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' 属性:%s\n", 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' 属性:%s\n", ps.getName(), ps.getProperty("user.name"));
        }

    }

}

参考资料

极客时间-《小马哥讲 Spring 核心编程思想》

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐