因为项目中需要用到多数据源,所以想自己写一个datasource.yml
的配置文件单独用来配置数据源,采用spring的常用解析方法,并没有成功。
datasource.yml
文件dynamic:
datasource:
druids:
write:
driverClassName: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://118.89.104.67:3306/saas_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: mysql!@root##123
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
DataSourceProperties
@Data
public class DataSourceProperties {
private String driverClassName;
private String url;
private String username;
private String password;
}
DynamicDataSourceProperties
使用spring提供的@ConfigurationProperties
配合@PropertySource
注解来将上面的datasource.yml
解析到DynamicDataSourceProperties
的druids
Map中
@Data
@PropertySource(value = {"classpath:datasource.yml"})
@ConfigurationProperties(prefix = "dynamic.datasource")
public class DynamicDataSourceProperties {
// 多数据源配置属性转Map
private Map<String, DataSourceProperties> druids = new LinkedHashMap<>();
}
但是结果很不理想,启动springboot
后发现driuds
是一个空的集合。
通过追踪@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 @PropertySource
annotation metadata.
* @param propertySource metadata for the @PropertySource
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"
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;
}
}
@PropertySource
注解使用的时候指定解析的PropertySourceFactory
@PropertySource(value = {"classpath:datasource.yml"},
factory = YamlPropertySourceFactory.class)