1. 背景
笔者的一个微服务的配置是ini文件中存储的。通过下面的方式加载。
@Data
@EqualsAndHashCode(callSuper = true)
@Component
@PropertySource(value={"file:${app.config.common.path}" , "file:${app.config.path}"} , ignoreResourceNotFound=false
, encoding="utf-8" , name="app-config" , factory = PropertiesExSourceFactory.class)
public class AppConfig extends AppConfCommon
{
@Value("${authcenter.appkey}")
String authCenterAppKey ;
@Value("${authcenter.appsecret}")
String authCenterAppSecret ;
@Value("${sailPyAi.url}")
String sailPyAiUrl ;
}
现在有一些必定数量的参数,它们都以固定的前缀开始,例如
ai.models.glm-4=xxxx
ai.models.bigwatt=xxx
想把ai.models.*的配置都收集到一起
2. 错误的尝试
此尝试,虽然失败,但里面有一些信息觉得有必要记录一下。
过程:
- 定义一个Map
public class AppConfig extends AppConfCommon
{
@Value("${authcenter.appkey}")
String authCenterAppKey ;
@Value("${authcenter.appsecret}")
String authCenterAppSecret ;
@Value("${sailPyAi.url}")
String sailPyAiUrl ;
@Value("${ai.models.*:}")
Map<String, Object> aiModels ;
}
- 修改PropertiesExSourceFactory,在渠道.*结尾的数据时构造成一个Map返回。
static class PropertiesExSource extends EnumerablePropertySource<PropertiesEx>
{
public PropertiesExSource(String name, PropertiesEx source)
{
super(name , source) ;
}
@Override
public Object getProperty(String aName)
{
String propValue = getSource().getProperty(aName) ;
if(propValue == null && aName.endsWith(".*"))
{
// 检查键
String name = aName.substring(0, aName.length()-1) ;
Map<String , Object> map = CS.hashMap() ;
PropertiesEx source= getSource() ;
for(String propName : source.stringPropertyNames())
{
if(propName.startsWith(name))
map.put(propName , source.getProperty(propName)) ;
}
if(!map.isEmpty())
// 因为Spring框架对@PropertySource源,认为取得的值类型一定是String,返回map会报Map转String错误。
// 所以此处把Map转成String格式
return new JSONObject(map).toJSONString() ;
}
return propValue ;
}
@Override
public String[] getPropertyNames()
{
return getSource().stringPropertyNames().toArray(XArray.sEmptyStringArray) ;
}
}
- 因为目标类型是Map,所以需要一个将JSON字符串转成Map的Converter
public class JsonStrToMapConverter implements Converter<String, Map<String, Object>>
{
public JsonStrToMapConverter()
{
}
@Override
public Map<String, Object> convert(String source)
{
return XString.isEmpty(source)?CS.hashMap():new JSONObject(source).toMap() ;
}
}
4.注册这个Converter
因为配置文件加载较早,所以用下面的方式添加Converter
public class DefaultAppRunLsn implements ApplicationListener<ApplicationEvent>
{
// 省略
@Override
public void onApplicationEvent(ApplicationEvent event)
{
if(event instanceof ApplicationPreparedEvent)
{
ConfigurableApplicationContext ctx = ((ApplicationPreparedEvent)event).getApplicationContext() ;
ConversionService cs = ctx.getBeanFactory().getConversionService() ;
if(cs instanceof ConverterRegistry)
{
ConverterRegistry reg = (ConverterRegistry)cs ;
reg.addConverter(new JsonStrToMapConverter());
}
// 省略
}
else if(event instanceof ServletWebServerInitializedEvent)
{
// 省略
}
}
}
这么做,如果只有一个PropertySource,没有问题,但是有多个的时候,会有问题,原因就是用@Value方式注入,多个PropetySource在一个池子里了,第2步中的getSource()不一定是当前@value字段所在的那个PropertySource了。
3. 正确做法
单独在定义一个配置类。如下:
@Component
@PropertySource(value="file:${app.config.path}" , ignoreResourceNotFound=false
, encoding="utf-8" , name="ai-models" , factory = PropertiesExSourceFactory.class)
@ConfigurationProperties(prefix = "ai")
public class AiModelsConf
{
Properties models ;
public Properties getModels()
{
return mConf;
}
public void seModels(Properties aConf)
{
mConf = aConf;
}
}