Spring @PropertySource用法和源码分析

用法

https://mkyong.com/spring/spring-propertysources-example/

核心类

Properties

在这里插入图片描述

Java中的Properties类实际上继承了Hashtable,它具有Map的特性。此外,还额外定义了一些方法:

  • load:从文件中加载键值对
  • getProperty:根据键获取值

PropertySource体系

在这里插入图片描述

PropertySource抽象类

public abstract class PropertySource<T> {
	protected final Log logger = LogFactory.getLog(getClass());
	protected final String name; // propertySource的名称
	protected final T source; // 存放键值对

官方文档:

Abstract base class representing a source of name/value property pairs. The underlying source object may be of any type T that encapsulates properties. Examples include java.util.Properties objects, java.util.Map objects, ServletContext and ServletConfig objects (for access to init parameters). Explore the PropertySource type hierarchy to see provided implementations.

该类主要用于封装键值对,键值对存放在该类的source域中,键值对source是一个泛型T,它可以是任意的可以封装键值对的类型,例如Properties,Map等等。

MapPropertySource类

底层的source基于Map结构的PropertySource,从其类签名可以看出:

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>>

PropertiesPropertySource类

底层的source基于Properties结构的PropertySource,从其类签名可以看出(注意:Properties实现了Map接口!):

public class PropertiesPropertySource extends MapPropertySource

ResourcePropertySource类

该类继承了PropertiesPropertySource,可以从资源对象(Resource)中加载键值对,并以Properties的格式存在其source字段中。

这里的资源可以是properties文件、xml文件等等。以其构造器为例:

public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
    // 第二个参数是source,通过PropertiesLoaderUtils.loadProperties从资源中加载
    super(name, PropertiesLoaderUtils.loadProperties(resource));
    this.resourceName = getNameForResource(resource.getResource());
}

PropertyResolver和Environment体系

在这里插入图片描述

PropertyResolver接口

该方法可以从任意形式的property source中解析property,主要包含下面3类方法:

  • containsProperty:property source是否包含某个key对应的property
  • getProperty:从property source获取某个key对应的property
  • resolvePlaceholders(text):解析text中的${}占位符

Environment接口

public interface Environment extends PropertyResolver {

	/**
	 * Return the set of profiles explicitly made active for this environment. 
	 */
	String[] getActiveProfiles();
	/**
	 * Return the set of profiles to be active by default when no active profiles have
	 * been set explicitly.
	 */
	String[] getDefaultProfiles();

    @Deprecated
	boolean acceptsProfiles(String... profiles);

	/**
	 * Return whether the {@linkplain #getActiveProfiles() active profiles}
	 * match the given {@link Profiles} predicate.
	 */
	boolean acceptsProfiles(Profiles profiles);

}

官方文档:

Interface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the PropertyResolver superinterface.

  • A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.
  • Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

Environment代表了当前应用的运行环境,该环境主要包括两个关键部分:profile和properties。(前者体现在该接口声明的几个profile相关的方法,后者体现在该接口从PropertyResolver继承来的方法)。

ConfigurablePropertyResolver接口

顾名思义,该接口相比PropertyResolver增加了一个可配置特性。可配置的点有:

  • 对property的值进行类型转换的机制:setConversionService/getConversionService
  • 解析占位符时的前缀和后缀:setPlaceholderPrefix/setPlaceholderSuffix
  • 必须存在的属性:setRequiredProperties
  • 等等

AbstractPropertyResolver抽象类

对ConfigurablePropertyResolver接口接口的默认实现

PropertySourcesPropertyResolver类

继承了AbstractPropertyResolver类,可以对多个property sources进行解析。看它的类名就知道,PropertySources是复数。

ConfigurableEnvironment接口

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {

前面提到了,Environment主要包含两方面的内容,一是profile,二是properties。

所谓ConfigurableEnvironment,顾名思义,它和Environment的接口的区别就在于可配置,Environment接口提供的方法只能对profile和properties相关的内容进行读取而不能进行设置

ConfigurableEnvironment相比Environment接口正是增加了对profile和properties的设置功能,它除了继承ConfigurablePropertyResolver接口的方法外,还额外了如下操作或设置profile和properties的方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7q4QEAz-1620551204438)(https://raw.githubusercontent.com/lvhlvh/pictures/master/img20210509160604.png)]

AbstractEnvironment抽象类

是对ConfigurableEnvironment接口的默认实现。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
	
    // 这几个PROPERTY_NAME我们可以通过java -D参数指定
	public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
	public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
	public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

    // 默认Profile的代码
	protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";


	protected final Log logger = LogFactory.getLog(getClass());

	private final Set<String> activeProfiles = new LinkedHashSet<>();

	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles()); 

	private final MutablePropertySources propertySources;

    // ★ 注意这里的propertyResolver(会从this.propertySources中对应的多个propertySource中解析)
    private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

StandardEnvironment类

该类继承了AbstractEnvironment类,做了如下扩展:

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value}. */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

}

该方法相比AbstractEnvironment,会将SystemProperties和SystemEnvironment添加到this.propertySources字段中

打断点观察注册的着两个propertySource结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CN3XFW04-1620551204445)(https://raw.githubusercontent.com/lvhlvh/pictures/master/img20210509165209.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iD6ToyHC-1620551204447)(https://raw.githubusercontent.com/lvhlvh/pictures/master/img20210509165259.png)]

源码

从@PropertySource的用法示例中我们知道,通过@PropertySource加载的外部配置除了可以通过@Value注入外,也存在于Environment对象中,可以从Environment对象中获取

Environment对象的创建

测试代码如下:

public class AppConfig02Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(AppConfig02.class);
    }
}

断点打在AbstractEnvironment的构造器中,得到如下调用链:

在这里插入图片描述

⚠️ 注意:对于不同的使用方式,上述创建Environment对象的调用链可能发生变化。例如使用Spring整合Junit测试时,调用链就不和上面一样。​

@PropertySource注解的解析

在ConfigurationClassParser.doProcessConfigurationClass方法解析配置类时,会对@PropertySource注解进行解析:

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass, filter);
    }

    // ★ 解析@PropertySource
    // Process any @PropertySource annotations
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            // ★ 解析@PropertySource
            processPropertySource(propertySource);
        }
        else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

下面来看看processPropertySource方法:

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

    // 获取@PropertySource的factory属性
    Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
    PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
                                     DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

    for (String location : locations) {
        try {
            // @PropertySource的value属性值中可能存在${}占位符, 下面这句解析占位符
            String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
            // 将路径解析为资源(org.springframework.core.io.DefaultResourceLoader.getResource)
            Resource resource = this.resourceLoader.getResource(resolvedLocation);
            // (1) factory.createPropertySource: 解析资源(properties文件)中的键/值对,封装到PropertySource对象中并返回
            //                                    PropertySource是对某一资源对应键值对的封装,键值对存储在其source字段中
            // (2) addPropertySource将property source注册到enviroment对象的propertySources字段中
            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;
            }
        }
    }
}

上述方法的factory.createPropertySource会创建PropertySource对象,我们知道,PropertySource底层的source字段封装了键值对。在该createPropertySource期间,会解析Resource对应的properties文件内容生成键值对。解析properties文件内容的调用链如下,最终会调用Properties对象的load方法:

在这里插入图片描述

上述方法的addPropertySource会将resource解析出来的PropertySource添加到Environment对象的propertySources字段中

environment.getProperty

我们可以通过Enviroment.getProperty来获取通过properties文件配置的键值对。该方法获取键值对的调用链如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L73TeICS-1620551204457)(C:\Users\Lv Hao\AppData\Roaming\Typora\typora-user-images\image-20210509170046673.png)]

在核心类分析的过程中看到了,AbstractEnvironment会使用PropertySourcesPropertyResolver作为自己的propertyResolver。因此最终会调用到PropertySourcesPropertyResolver.getProperty方法来获取键对应的值:

@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        // 遍历this.propertySources (即Environment中每一个propertySource),
        // 尝试从每一个propertySource中获取指定键对应的值
        for (PropertySource<?> propertySource : this.propertySources) {
            if (logger.isTraceEnabled()) {
                logger.trace("Searching for key '" + key + "' in PropertySource '" +
                             propertySource.getName() + "'");
            }
            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);
            }
        }
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Could not find key '" + key + "' in any property source");
    }
    return null;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值