在使用 Spring 2.0 的 PropertyPlaceholderConfigurer 時,若發現 XML 配置中的 ${jdbc.name} 以原樣字符串輸出而非屬性值,通常是由於 BeanFactory 與 ApplicationContext 的差異 導致後置處理器未自動觸發。本文深度剖析源碼機制,提供三種解決方案並對比優劣。
<!-- applicationContext.xml -->
<bean id="iDaoImp" class="service.IDaoImp">
<property name="name" value="${jdbc.name}"/>
</bean>
<bean id="property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
<!-- jdbc.properties -->
jdbc.name=王昊
// 測試代碼
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
IDaoImp imp = (IDaoImp) beanFactory.getBean("iDaoImp");
System.out.println(imp.getName()); // 輸出:${jdbc.name} ❌
Spring 容器體系分兩層:
BeanFactoryPostProcessor(如 PropertyPlaceholderConfigurer)BeanFactoryPostProcessor 與 BeanPostProcessor源碼證據(AbstractApplicationContext.refresh()):
// 1. 調用 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 2. 註冊 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 3. 初始化單例 Bean(此時 ${...} 已被替換)
finishBeanFactoryInitialization(beanFactory);
而 XmlBeanFactory 僅完成 Bean 定義讀取,**完全跳過** 步驟 1,導致佔位符未解析。
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
PropertyPlaceholderConfigurer ppc = (PropertyPlaceholderConfigurer) beanFactory.getBean("property");
ppc.postProcessBeanFactory(beanFactory); // 手動調用
IDaoImp imp = (IDaoImp) beanFactory.getBean("iDaoImp"); // 輸出:王昊 ✅
缺點:侵入業務代碼,違背 IoC 原則,若有多個後置處理器需手動排序調用,維護成本極高。
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
IDaoImp imp = ctx.getBean("iDaoImp", IDaoImp.class);
System.out.println(imp.getName()); // 輸出:王昊 ✅
優點:零代碼修改,自動完成所有後置處理,符合 Spring 設計初衷,支援國際化、事件發布、AOP 等企業級特性。
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 自動註冊 PropertySourcesPlaceholderConfigurer,功能更強 -->
支援多文件、系統環境變量覆蓋、默認值、文件編碼等高級特性。
| 誤區 | 真相 |
|---|---|
| 以為 PropertyPlaceholderConfigurer 是 BeanPostProcessor | 它是 BeanFactoryPostProcessor,**在 Bean 實例化前** 修改 BeanDefinition 的屬性值 |
以為 value="${jdbc.name}" 是 SpEL 表達式 | 它是 Spring 專用佔位符語法,解析器為 PropertyPlaceholderHelper,非 SpEL |
| 覺得 XmlBeanFactory 輕量適合測試 | 測試也應用 ApplicationContext,或使用 @ContextConfiguration 整合 Spring Test |
<context:property-placeholder> 或 @PropertySource 註解「Spring 的強大在於約定優於配置,但前提是你用對了容器。BeanFactory 是 SPI 接口,ApplicationContext 才是開發者應直接面對的編程模型。」