Spring&SpringBoot properties
先看看原情况以及效果图
image.png说到乱码问题,其实目前遇到的乱码问题原因可以归根为读取协议不对等,例如使用GBK文件格式去读取UTF-8文件格式的内容肯定会出现乱码,那么我们解决问题的整体思路也就是使用合适的读取格式去读取文件内容
本篇学习笔记代码地址在simple-spring 的spring-learn模块的bpp文件夹中
Properties 文件读取
在之前的学习笔记中Spring Properties属性获取 源码学习,也曾经介绍过如何读取@Value("${XXX}")
在属性文件中XXX对应的值。
PropertySourcesPlaceholderConfigurer 类是实现BeanFactoryPostProcessor接口的,在执行postProcessBeanFactory方法的时候,会进行属性文件的读取并且把键值对信息存储到PropertySourcesPlaceholderConfigurer类本身的容器中
在抽象类PropertiesLoaderSupport的protected void loadProperties(Properties props) throws IOException
方法去完成属性文件内容的读取
最后来到了PropertiesLoaderUtils工具类中
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
throws IOException {
InputStream stream = null;
Reader reader = null;
try {
String filename = resource.getResource().getFilename();
// 获取文件名称
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
// 1处 文件名不为null,而且是xml结尾
stream = resource.getInputStream();
persister.loadFromXml(props, stream);
}
else if (resource.requiresReader()) {
// 2处 如果resource中的encoding字段或者charset字段不为空
// 就会使用encoding内的字符编码或者charset设置的字符编码格式读取属性文件
reader = resource.getReader();
persister.load(props, reader);
}
else {
// 3处 否则就是使用默认的字符编码获取IO流
stream = resource.getInputStream();
persister.load(props, stream);
}
}
所以现在能够解决当前问题只有2处了,需要设置resource对象的encoding、charset字段值,而此resource生成是使用了new EncodedResource(location, this.fileEncoding)
实现的
现在解决方案已经非常明了了,也就是设置PropertySourcesPlaceholderConfigurerbean的fileEncoding字段值即可
fileEncoding 字段值填充
xml配置的方法还是很简单的,直接设置bean的基本参数,然后在bean实例完之后参数填充即可
image现在讨论的是另一种情况,没有对外直接的file-encoding字段设置能力
上面已经说了,调用postProcessBeanFactory方法,会进行属性文件的读取(也就意味着PropertySourcesPlaceholderConfigurer是一个实现了BeanFactoryPostProcessor和PriorityOrdered接口的特殊BPP类),那么就必须在这个方法调用前完成参数的注入
重点关注BPP处理的核心类PostProcessorRegistrationDelegate
可以提出两种解决方案
- 实现BeanDefinitionRegistryPostProcessor 接口
- 实现BeanFactoryPostProcessor、PriorityOrdered 接口(排序超过PropertySourcesPlaceholderConfigurer)
至于为啥是这两种方案,可以看看之前写的Spring 钩子之BeanFactoryPostProcessor和BeanPostProcessor的源码学习 、再谈Spring BeanPostProcessor
例如如下自定义的BPP(具方法可看github内的代码)
@Component
public class PriorityOrderedBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
PriorityOrdered {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
Map map = beanFactory.getBeansOfType(PropertySourcesPlaceholderConfigurer.class);
// 可能存在多个PropertySourcesPlaceholderConfigurerbean的情况
Iterator<Map.Entry<String, PropertySourcesPlaceholderConfigurer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, PropertySourcesPlaceholderConfigurer> entry = it.next();
PropertySourcesPlaceholderConfigurer pp = entry.getValue();
pp.setFileEncoding("UTF-8");
// 终于获取到了设置文件编码类型的地方了
// 这也是最关键的地方
}
}
@Override
public int getOrder() {
// 排序规则,具体可以看看OrderComparator的排序规则
// PropertySourcesPlaceholderConfigurer的排序order是最低的,所以设置一个0完全可以满足要求
return 0;
}
}
总结
本学习笔记也可以算是又一篇BPP的实践,一方面加深对BPP的了解,另一方面结合当前遇到的具体问题具体解决,其实随着对spring学习的深入会发现越来越多通过BPP扩展的功能,例如监控spring-actuator也是利用BPP添加额外的端点完成相应的功能
如果对BPP有更多兴趣的,PostProcessorRegistrationDelegate类需要来回琢磨,调试