先看看原情況以及效果圖
說到亂碼問題剃盾,其實目前遇到的亂碼問題原因可以歸根為讀取協(xié)議不對等,例如使用GBK文件格式去讀取UTF-8文件格式的內(nèi)容肯定會出現(xiàn)亂碼贝咙,那么我們解決問題的整體思路也就是使用合適的讀取格式去讀取文件內(nèi)容
本篇學(xué)習(xí)筆記代碼地址在simple-spring 的spring-learn模塊的bpp文件夾中
在此感謝 antz_H 的指正讶泰,此筆記只是本人在學(xué)習(xí)調(diào)試Spring代碼時弄的馒闷,啟動的方法也是ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext( new String[]{"spring/spring-bpp.xml"})
不適用于SpringBoot常規(guī)的情況下,SpringBoot已經(jīng)提供了現(xiàn)成的@PropertySource
注解 解決方案
Properties 文件讀取
在之前的學(xué)習(xí)筆記中Spring Properties屬性獲取 源碼學(xué)習(xí)归榕,也曾經(jīng)介紹過如何讀取@Value("${XXX}")
在屬性文件中XXX對應(yīng)的值尸红。
PropertySourcesPlaceholderConfigurer 類是實現(xiàn)BeanFactoryPostProcessor接口的,在執(zhí)行postProcessBeanFactory方法的時候刹泄,會進行屬性文件的讀取并且把鍵值對信息存儲到PropertySourcesPlaceholderConfigurer類本身的容器中
在抽象類PropertiesLoaderSupport的protected void loadProperties(Properties props) throws IOException
方法去完成屬性文件內(nèi)容的讀取
最后來到了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結(jié)尾
stream = resource.getInputStream();
persister.loadFromXml(props, stream);
}
else if (resource.requiresReader()) {
// 2處 如果resource中的encoding字段或者charset字段不為空
// 就會使用encoding內(nèi)的字符編碼或者charset設(shè)置的字符編碼格式讀取屬性文件
reader = resource.getReader();
persister.load(props, reader);
}
else {
// 3處 否則就是使用默認(rèn)的字符編碼獲取IO流
stream = resource.getInputStream();
persister.load(props, stream);
}
}
所以現(xiàn)在能夠解決當(dāng)前問題只有2處了,需要設(shè)置resource對象的encoding特石、charset字段值盅蝗,而此resource生成是使用了new EncodedResource(location, this.fileEncoding)
實現(xiàn)的
現(xiàn)在解決方案已經(jīng)非常明了了,也就是設(shè)置PropertySourcesPlaceholderConfigurerbean的fileEncoding字段值即可
fileEncoding 字段值填充
xml配置的方法還是很簡單的,直接設(shè)置bean的基本參數(shù)姆蘸,然后在bean實例完之后參數(shù)填充即可
現(xiàn)在討論的是另一種情況墩莫,沒有對外直接的file-encoding字段設(shè)置能力
上面已經(jīng)說了芙委,調(diào)用postProcessBeanFactory方法,會進行屬性文件的讀取(也就意味著PropertySourcesPlaceholderConfigurer是一個實現(xiàn)了BeanFactoryPostProcessor和PriorityOrdered接口的特殊BPP類)贼穆,那么就必須在這個方法調(diào)用前完成參數(shù)的注入
重點關(guān)注BPP處理的核心類PostProcessorRegistrationDelegate
可以提出兩種解決方案
- 實現(xiàn)BeanDefinitionRegistryPostProcessor 接口
- 實現(xiàn)BeanFactoryPostProcessor题山、PriorityOrdered 接口(排序超過PropertySourcesPlaceholderConfigurer)
至于為啥是這兩種方案,可以看看之前寫的Spring 鉤子之BeanFactoryPostProcessor和BeanPostProcessor的源碼學(xué)習(xí) 故痊、再談Spring BeanPostProcessor
例如如下自定義的BPP(具方法可看github內(nèi)的代碼)
@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");
// 終于獲取到了設(shè)置文件編碼類型的地方了
// 這也是最關(guān)鍵的地方
}
}
@Override
public int getOrder() {
// 排序規(guī)則顶瞳,具體可以看看OrderComparator的排序規(guī)則
// PropertySourcesPlaceholderConfigurer的排序order是最低的,所以設(shè)置一個0完全可以滿足要求
return 0;
}
}
總結(jié)
本學(xué)習(xí)筆記也可以算是又一篇BPP的實踐愕秫,一方面加深對BPP的了解慨菱,另一方面結(jié)合當(dāng)前遇到的具體問題具體解決,其實隨著對spring學(xué)習(xí)的深入會發(fā)現(xiàn)越來越多通過BPP擴展的功能戴甩,例如監(jiān)控spring-actuator也是利用BPP添加額外的端點完成相應(yīng)的功能
如果對BPP有更多興趣的符喝,PostProcessorRegistrationDelegate類需要來回琢磨,調(diào)試