技巧!Spring Boot生產環境重新初始化Bean
1. 簡介
在本篇文章中,我將介紹在運行時重新初始化單例 Spring Bean 的方法。默認情況下,具有單例作用域的 Spring Bean 不會在應用程序生命周期中重新初始化。不過,有時可能需要重新創建 Bean,例如在更新屬性時。我將介紹幾種實現此功能的方法。
2. 實戰案例
為了演示我們將創建一個bean,該Bean將從配置文件中讀取配置屬性。如果文件中的屬性發生變化,則對該Bean進行重新初始化以便得到最新的數據。
2.1 單例Bean定義
@Component
public class ConfigManager {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class) ;
private Map<String, Object> config = new HashMap<>() ;
// 配置的是具體值是絕對路徑
private final String filePath ;
public ConfigManager(@Value("${pack.app.filePath}") String filePath) {
this.filePath = filePath ;
// 創建該bean對象時,加載配置文件信息
initConfig() ;
}
private void initConfig() {
Properties properties = new Properties() ;
try {
properties.load(Files.newInputStream(Paths.get(filePath))) ;
} catch (IOException e) {
logger.error("錯誤的加載配置文件, {}", e) ;
}
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) ;
}
}
接下來,在classpath下新建config.properties配置文件,配置內容如下:
pack:
app:
filePath: d:/pack/config.properties
下面我們可以定義一個Controller該測試當前的配置是否有問題。
@RestController
@RequestMapping("/config")
public class ConfigController {
@Autowired
private ConfigManager configManager;
@GetMapping("/{key}")
public Object get(@PathVariable String key) {
return configManager.getConfig(key);
}
}
默認配置文件內容
title=xxxooo1
訪問接口
圖片
目前,上面的接口不管配置如何修改,在不重啟服務的情況下都無法得到最新的值;接下來我將通過幾種方式來演示如何去刷新最新的配置。
2.2 通過公共方法刷新
如果我們想要重新加載屬性而不是重新創建對象本身,我們可以簡單地創建一個公共方法來再次初始化。在我們的ConfigManager中,讓我們添加一個調用reloadConfig()方法的方法:
public void reloadConfig() {
initConfig() ;
}
然后,當我們要重新加載屬性時,就可以調用該方法。接著在Controller中定義另一個接口,該接口調用 reloadConfig()方法:
@GetMapping("/reloadConfig")
public void reloadConfig() {
configManager.reloadConfig() ;
}
通過測試上面的代碼,你將得到最新的配置。此種方法也是最容易想到的辦法。
2.3 使用@Lazy注解
我們可以使用@Lazy注解添加到注入的ConfigManager對象的地方,如下示例:
@Resource
@Lazy
private ConfigManager configManager;
@Value("${pack.app.filePath}")
private String filePath ;
@GetMapping("/reinitializeBean")
public void reinitializeBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) context.getAutowireCapableBeanFactory() ;
// 銷毀bean;銷毀后當再次使用該bean時容器會再次執行整個創建過程
registry.destroySingleton("configManager") ;
}
當配置發生變化后,先調用上面的/reinitializeBean接口,這會先把單例池中的實例刪除,當再次調用/title接口時就會重新創建對象了。
2.4 通過容器獲取Bean
我們可以將對應的bean銷毀,然后在使用的時候再次從容器中獲取,這時候由于已經將該單例bean銷毀,單例池中已經沒有了,所以會重新創建。
@Resource
private ApplicationContext context ;
@Value("${pack.app.filePath}")
private String filePath ;
@GetMapping("/destroyBean")
public void destroyBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory() ;
registry.destroySingleton("configManager") ;
}
接下來修改使用獲取數據的接口
@GetMapping("/{key}")
public Object get(@PathVariable String key) {
ConfigManager cm = context.getBean(ConfigManager.class) ;
return cm.getConfig(key) ;
}
如果配置文件修改后,我們先調用/destroyBean接口,這樣當我們調用/title接口時,將會獲取到最新的配置。