許多開發者認為 Spring 配置只能靜態設置參數 (數據源、定時器等),實則 Spring 提供 FactoryBean 機制優雅解決動態配置需求。
public interface FactoryBean {
Object getObject() throws Exception; // 返回管理的對象實例 (單例/原型)
Class getObjectType(); // 返回對象類型,供自動裝配檢查
boolean isSingleton(); // 是否單例 (true=getObject() 返回同一引用)
}
關鍵語義:實現 FactoryBean 的 Bean 本身不作為最終 Bean 使用,容器調用其 getObject() 返回的對象才是真正注入的 Bean。框架內大量使用 (AOP ProxyFactoryBean、JNDI JndiObjectFactoryBean、Hibernate LocalSessionFactoryBean)。
場景:數據庫連接參數存放於外部配置中心/加密文件/環境變量,啟動時動態讀取組裝 BasicDataSource。
public class BasicDataSourceFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
BasicDataSource dataSource = new BasicDataSource();
// 1. 從外部源讀取配置 (配置中心、環境變量、加密文件、Consul/Etcd 等)
// 2. 設置屬性
dataSource.setDriverClassName(...);
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
dataSource.setInitialSize(...);
// ... 其他連接池參數
return dataSource;
}
public Class getObjectType() {
return BasicDataSource.class;
}
public boolean isSingleton() {
return true; // 數據源通常為單例
}
}
<bean id="dataSource" class="com.example.BasicDataSourceFactoryBean">
<!-- 可注入配置源引用:Properties、Environment、ConfigClient 等 -->
<property name="configLocation" value="classpath:db.properties"/>
<property name="decryptor" ref="passwordDecryptor"/>
</bean>
容器啟動時調用 getObject(),獲得動態構建的 DataSource,其他 Bean 引用 dataSource 時拿到的是真實連接池而非 FactoryBean 本身。
getObject() 內解密密碼後設置lazy-init="true" 配合 FactoryBean 實現真正的按需創建class="...FactoryBean" 的 Bean 定義getObject() 獲取真實對象isSingleton()=true)@Autowired DataSource 或 <ref bean="dataSource"/> 獲得真實對象這正是 JndiObjectFactoryBean、LocalSessionFactoryBean 等框架內建組件的工作原理。
getObjectType() 務必正確返回類型,否則自動裝配 (byType) 會忽略該 BeanisSingleton() 誠實聲明:若返回 true 但每次 getObject() 創建新實例,會導致緩存不一致singleton (默認),getObject() 返回的對象單例狀態由 isSingleton() 決定getObject() 拋出異常會被包裝為 BeanCreationException,上層可捕獲處理FactoryBean 是 Spring 「配置即代碼」能力的核心擴展點。善用它,XML 配置不再是靜態聲明,而是動態對象工廠的入口,讓 Spring 配置真正「活」起來。