Spring无法使用@PropertySource配合@ConfigurationProperties注入对象属性问题解析

一、问题背景

因为项目中需要用到多数据源,所以想自己写一个datasource.yml的配置文件单独用来配置数据源,采用spring的常用解析方法,并没有成功。

二、重现问题
2.1 编写datasource.yml文件
dynamic:
  datasource:
    druids:
      write:
        driverClassName: com.p6spy.engine.spy.P6SpyDriver
        url: jdbc:p6spy:mysql://127.0.0.1:3306/saas_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
        username: root
        password: 1234
        filters: stat
      read:
        driverClassName: com.p6spy.engine.spy.P6SpyDriver
        url: jdbc:p6spy:mysql://localhost:3306/saas_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
        username: root
        password: 1234
        filters: stat
2.2 编写DataSourceProperties
@Data
public class DataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}
2.3 编写DynamicDataSourceProperties

使用spring提供的@ConfigurationProperties配合@PropertySource注解来将上面的datasource.yml解析到DynamicDataSourcePropertiesdruidsMap中

@Data
@PropertySource(value = {"classpath:datasource.yml"})
@ConfigurationProperties(prefix = "dynamic.datasource")
public class DynamicDataSourceProperties {
	// 多数据源配置属性转Map
    private Map<String, DataSourceProperties> druids = new LinkedHashMap<>();
}

但是结果很不理想,启动springboot后发现driuds是一个空的集合。

2.4 无法注入原因解析

通过追踪@PropertySource的源码发现他的解析位置是从ConfigurationClassParser开始的,通过doProcessConfigurationClass方法来解析

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)

继续观察他的源码

// 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");
			}
		}

这里用了一个processPropertySource的方法,继续往下追踪

	/**
	 * Process the given <code>@PropertySource</code> annotation metadata.
	 * @param propertySource metadata for the <code>@PropertySource</code> annotation found
	 * @throws IOException if loading a property source failed
	 */
	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);
				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;
				}
			}
		}
	}

可以看到for循环里有这样一个函数addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));

里面具体解析属性文件的函数是由PropertySourceFactory factory 来执行的,跟踪下去,可以看到Spring只提供一个默认的DefaultPropertySourceFactory,观察这个默认的工厂不难发现他是用的PropertiesLoaderUtils.loadProperties(resource)来加载属性文件,跟踪到最后发现,在Properties这个类中读取属性

    public synchronized void load(InputStream inStream) throws IOException {
        load0(new LineReader(inStream));
    }

由此可以发现Spring提供的@PropertySource注解他并没有兼容yml文件的解析,他会一行一行的读取,这意味着

city:
	name: 海南
	area: 200

上面的yml会被解析为

city = ""
name = "海南"
area = "200"

而我们想要的是

city.name = "海南"
city.area = "200"
三、解决方案
3.1 自定义PropertySourceFactory
public class YamlPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
        List<PropertySource<?>> load = loader.load(name, resource.getResource());
        if (load!=null && !load.isEmpty()) {
            return load.get(0);
        }
        return null;
    }
}

3.2 使用@PropertySource注解

使用的时候指定解析的PropertySourceFactory


@PropertySource(value = {"classpath:datasource.yml"}, 
							 factory = YamlPropertySourceFactory.class)
  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值