在resource下创建自定义yml文件user.yml,文件内容如下
user:
id: 1
name: zhangshan
email: desmond@163.com
然后创建Customer类读取配置文件
@PropertySource(value = "user.yml")
@ConfigurationProperties(prefix = "user")
@Configuration
public class Customer {
private Integer id;
private String name;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
最后在controller中测试能否获取到user.yml文件中的值
从图中可以看出,Custom的Bean对象没有读到user.yml文件中的值,那为什么name会有Annsky呢
从源码中可以看到,读取xml文件配置并将相应的值赋给对应bean上是由ConfigurationClassParser这个类实现的,首先给Bean赋值方法如下
protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
this.processMemberClasses(configClass, sourceClass, filter);
}
Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();
AnnotationAttributes importResource;
while(var4.hasNext()) {
importResource = (AnnotationAttributes)var4.next();
if (this.environment instanceof ConfigurableEnvironment) {
//这个方法是关键的方法
this.processPropertySource(importResource);
} else {
this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
}
}
/*****后面就不贴了
}
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 : (PropertySourceFactory)BeanUtils.instantiateClass(factoryClass);
String[] var8 = locations;
int var9 = locations.length;
for(int var10 = 0; var10 < var9; ++var10) {
String location = var8[var10];
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
//关键在这个方法,这个方法是将user.yml配置解析为一个PropertySource类,并加入到集合中
this.addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
} catch (FileNotFoundException | UnknownHostException | SocketException | IllegalArgumentException var14) {
if (!ignoreResourceNotFound) {
throw var14;
}
if (this.logger.isInfoEnabled()) {
this.logger.info("Properties location [" + location + "] not resolvable: " + var14.getMessage());
}
}
}
}
截图中可以看到yml文件读到四个键值对,这是错误的,正确应该是user.name ---> "zhangsan",三条这样的类型数据,那这是为什么,返回到上我上面的代码
this.addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
参数是由factory.createPropertySource(name, new EncodedResource(resource, encoding))创建出来的,那么回头上去看
factory是DefaultPropertySourceFactory,这个工厂是不能解析yaml类型的文件的数据
虽然能读取user.yml,但是结果如第一张图所示,完全读成错误的数据,那么怎么办呢?
解决思路
既然是工厂不对,那么我们可以创建自定义的工厂
public class CustomYmlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
PropertySource custmoSetting = name != null ?
new YamlPropertySourceLoader().load(name,resource.getResource()).get(0)
: new YamlPropertySourceLoader().load(getNameForResource(resource.getResource()),resource.getResource()).get(0);
return custmoSetting;
}
private static String getNameForResource(Resource resource) {
String name = resource.getFilename();
if (!org.springframework.util.StringUtils.hasText(name)) {
name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
}
return name;
}
}
然后杳然Custom类指定由我们自定义的工厂来创建并解析配置文件然后赋值
@PropertySource(value = "user.yml",encoding = "utf-8",factory = CustomYmlPropertySourceFactory.class)
@ConfigurationProperties(prefix = "user")
@Configuration
public class Customer {
private Integer id;
private String name;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
这样加入到PropertySource集合中的Bean对象就是正确的数据
但是Custom对象的值中name的值还是不对,还是Annnsky
这是因为在系统环境变量配置有同名的user.name
所以应该怎么解决呢? 思路:首先将user.yml文件中的配置名修改成不冲突的,第二种将自己的自定义的yml文件解析出来的PropertySource配置类加到MutablePropertySources中PropertySourceList集合中的首节点,那么按照顺序加载文件并赋值的规则,自定义的user.name 优先于systemProperties中的user.name 第二种解决办法代码如下
public class ParserBeanfactoryprocesser implements BeanFactoryPostProcessor, ApplicationContextAware, BeanPostProcessor {
private ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
printPropertySourceNames();
String propertySourceName = "user.yml";
PropertySource propertySource = getPropertySource(propertySourceName);
if(!ObjectUtils.isEmpty(propertySource)){
//bean初始化之前,修改属性文件加载顺序
getEnvironment().getPropertySources().remove(propertySourceName);
getEnvironment().getPropertySources().addFirst(propertySource);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private AbstractEnvironment getEnvironment(){
return (AbstractEnvironment)applicationContext.getEnvironment();
}
private PropertySource getPropertySource(String propertySourceName) {
return getEnvironment().getPropertySources().get(propertySourceName);
}
private void printPropertySourceNames(){
getEnvironment().getPropertySources().stream().forEach(p-> System.out.println(p.getName()));
}
}