1. 概述
本教程將探討如何在運行時重新初始化單例 Spring Bean 的方法。
默認情況下,具有 singleton 作用域的 Spring Bean 在應用程序生命週期中不會被重新初始化。但是,在某些情況下,可能需要重新創建 Bean——例如,當屬性被更新時。我們將介紹幾種實現此目的的方法。
2. 代碼設置
為了理解這一點,我們將創建一個小型項目。我們將創建一個 Bean,該 Bean 從配置文件讀取配置屬性並將其保存在內存中以進行更快速的訪問。如果文件的屬性發生更改,可能需要重新加載配置。
2.1. 單例 Bean
讓我們首先創建一個 ConfigManager 類:
@Service("ConfigManager")
public class ConfigManager {
private static final Log LOG = LogFactory.getLog(ConfigManager.class);
private Map<String, Object> config;
private final String filePath;
public ConfigManager(@Value("${config.file.path}") String filePath) {
this.filePath = filePath;
initConfigs();
}
private void initConfigs() {
Properties properties = new Properties();
try {
properties.load(Files.newInputStream(Paths.get(filePath)));
} catch (IOException e) {
LOG.error("Error loading configuration:", e);
}
config = new HashMap<>();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
config.put(String.valueOf(entry.getKey()), entry.getValue());
}
}
public Object getConfig(String key) {
return config.get(key);
}
}
請注意以下幾點關於此類的:
- 從構造函數中調用方法 initConfigs() 會在 Bean 被構造時立即加載文件。
- 方法 initConfigs() 將文件的內容轉換為一個名為 config 的 Map。
- 方法 getConfig() 用於通過其鍵讀取屬性。
另一個需要注意的點是構造函數依賴注入。稍後我們會使用它來替換 Bean。
配置文件的路徑為 src/main/resources/config.properties,其中包含一個單一的屬性:
property1=value1
2.2. 控制器
為了測試ConfigManager,我們創建一個控制器:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Autowired
private ConfigManager configManager;
@GetMapping("/{key}")
public Object get(@PathVariable String key) {
return configManager.getConfig(key);
}
}
我們可以運行應用程序並通過訪問 URL http://localhost:8080/config/property1 讀取配置。
接下來,我們希望更改文件的屬性值,並在再次通過相同的 URL 讀取配置時反映該更改。下面我們將探討幾種實現方法。
3. 使用公共方法重新加載屬性
如果我們希望重新加載屬性而不重新創建對象本身,只需創建一個公共方法,該方法重新初始化地圖即可。 在我們的 ConfigManager 中,讓我們添加一個調用 initConfigs() 方法的方法:
public void reinitializeConfig() {
initConfigs();
}
我們可以隨後調用此方法來重新加載屬性。 讓我們在控制器類中暴露另一個方法,該方法調用 reinitializeConfig() 方法:
@GetMapping("/reinitializeConfig")
public void reinitializeConfig() {
configManager.reinitializeConfig();
}
我們現在可以運行該應用程序並進行測試,只需遵循以下幾個簡單步驟:
- 訪問 URL http://localhost:8080/config/property1,返回 value1。
- 然後,我們將 property1 的值從 value1 更改為 value2。
- 我們可以通過訪問 URL http://localhost:8080/config/reinitializeConfig 來重新初始化 ConfigMap。
- 如果再次訪問 URL http://localhost:8080/config/property1,我們會發現返回的值是 value2。
4. 重新初始化單例 Bean
另一種重新初始化 Bean 的方法是在上下文中重新創建它。重新創建可以通過使用自定義代碼和調用構造函數,或者刪除 Bean 並讓上下文自動重新初始化它來實現。下面我們分別來看這兩種方法。
4.1. 從上下文中替換 Bean
我們可以從上下文中刪除 Bean 並用一個新的 ConfigManager 實例替換它。 讓我們定義另一個方法在我們的控制器中執行此操作:
@GetMapping("/reinitializeBean")
public void reinitializeBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("ConfigManager");
registry.registerSingleton("ConfigManager", new ConfigManager(filePath));
}
首先,我們從應用程序上下文中獲取 DefaultSingletonBeanRegistry 實例。接下來,我們調用 destroySingleton() 方法來銷燬名為 ConfigManager 的 Bean 實例。最後,我們創建 ConfigManager 的新實例並將其註冊到工廠中,通過調用 registerSingleton() 方法實現。
為了創建新實例,我們使用了 ConfigManager 中定義的構造函數。 Bean 依賴於的其他任何依賴項必須通過構造函數傳遞。
registerSingleton() 方法不僅在上下文中創建 Bean,還將其自動注入到依賴對象中。
調用 /reinitializeBean 端點會更新控制器中的 ConfigManager Bean。我們可以使用與先前方法中相同的步驟來測試重新初始化行為。
4.2. 在上下文中銷燬 Bean
在之前的示例中,我們需要通過構造函數傳遞依賴項。有時,我們可能不需要創建 Bean 的新實例,或者可能無法訪問所需的依賴項。在這種情況下,僅僅銷燬 Bean 在上下文中也是一種可能性。
上下文會在 Bean 再次請求時重新創建 Bean。在這種情況下,它將使用與初始 Bean 創建相同的步驟。
為了演示這一點,讓我們創建一個銷燬 Bean 但不會重新創建它的新控制器方法:
@GetMapping("/destroyBean")
public void destroyBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("ConfigManager");
}
這不會改變控制器已持有的 Bean 的引用。我們需要直接從上下文中讀取它。
讓我們創建一個新的控制器來讀取配置。這個控制器將依賴於上下文中最新的 Bean:
@GetMapping("/context/{key}")
public Object getFromContext(@PathVariable String key) {
ConfigManager dynamicConfigManager = applicationContext.getBean(ConfigManager.class);
return dynamicConfigManager.getConfig(key);
}
我們可以使用以下幾個簡單步驟測試上述方法:
- 訪問 URL http://localhost:8080/config/context/property1 返回 value1。
- 然後,我們可以訪問 URL http://localhost:8080/config/destroyBean 來銷燬 ConfigManager。
- 接下來,我們將 property1 的值從 value1 更改為 value2。
- 如果再次訪問 URL http://localhost:8080/config/context/property1, 我們會發現返回的值是 value2。
5. 結論
在本文中,我們探討了如何重新初始化單例 Bean 的方法。我們研究了在不重新創建 Bean 的情況下修改 Bean 屬性的一種方式。我們還研究了在上下文中強制重新創建 Bean 的方法。