1、概览 {#1概览}
在本教程中,我们将了解如何在运行时重新初始化 Spring Context 中的 Singleton Bean。
默认情况下,Singleton Scope Bean 不会在应用生命周期中重新初始化。不过,有时可能需要重新创建 Bean,例如在需要更新属性时。我们将介绍几种实现此目的的方法。
2、示例 {#2示例}
一个小示例。创建一个 Bean,从配置文件中读取属性保存在内存中。如果配置文件中的属性值被修改,那么 Bean 就要重新加载配置。
2.1、Singleton Bean {#21singleton-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()
方法用于通过 key 值读取属性。
注意,这里使用了基于构造函数的依赖注入。当需要替换 Bean 时,这很重要。
配置文件的路径为 src/main/resources/config.properties
,包含一个属性:
property1=value1
2.2、Controller {#22controller}
创建一个 controller 来测试 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、使用 public 方法重写加载属性 {#3使用-public-方法重写加载属性}
如果我们想重新加载属性,而不是重新创建对象本身,只需创建一个再次初始化 map 的公共方法即可。
在 ConfigManager
中添加一个方法,调用 initConfigs()
方法:
public void reinitializeConfig() {
initConfigs();
}
然后,当我们要重新加载属性时,就可以调用该方法。让我们在 Controller 中公开另一个调用 reinitializeConfig()
方法的方法:
@GetMapping("/reinitializeConfig")
public void reinitializeConfig() {
configManager.reinitializeConfig();
}
现在,我们可以通过如下步骤进行测试:
- 访问 URL
http://localhost:8080/config/property1
,返回value1
。 - 然后,修改配置文件,把
property1
的值从value1
改为value2
。 - 然后,访问 URL
http://localhost:8080/config/reinitializeConfig
来重新初始化 config map。 - 再次访问 URL
http://localhost:8080/config/property1
,就会发现返回的值是value2
。
4、重新初始化 Singleton Bean {#4重新初始化-singleton--bean}
重新初始化 Bean 的另一种方式是通过在 Context 中重新创建它。重新创建可以通过使用自定义代码调用构造函数或删除 Bean 并让 Context 自动重新初始化来完成。让我们看一下这两种方式。
4.1、替换 context 中的 Bean {#41替换-context-中的-bean}
我们可以从 Context 中删除 Bean,并用 ConfigManager
的新实例取而代之。让我们在 Controller 中定义另一个方法来实现:
@GetMapping("/reinitializeBean")
public void reinitializeBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("ConfigManager");
registry.registerSingleton("ConfigManager", new ConfigManager(filePath));
}
首先,我们从 Application Context 中获取 DefaultSingletonBeanRegistry
的实例。然后,我们调用 destroySingleton()
方法销毁名为 ConfigManager
的 Bean 实例。最后,我们创建一个 ConfigManager
的新实例,并调用 registerSingleton()
方法将其注册到 Factory。
ConfigManager
Bean 所依赖的任何依赖都必须通过构造函数传递。
registerSingleton()
方法不仅在 Context 中创建 Bean,还会自动将其注入到依赖的对象中。
调用 /reinitializeBean
端点可更新 Controller 中的 ConfigManager
Bean。我们可以使用与上述相同的步骤进行测试。
4.2、在 Context 中销毁 Bean {#42在-context-中销毁-bean}
在前面的方法中,我们需要通过构造函数传递依赖。有时,我们可能不需要创建 Bean 的新实例,或者无法访问所需的依赖。在这种情况下,另一种方法就是在 Context 中销毁 Bean。
当再次请求 Bean 时,Context 将负责再次创建 Bean。在这种情况下,将使用与创建初始 Bean 相同的步骤创建 Bean。
为了演示这一点,让我们创建一个新的 Controller 方法来销毁 Bean,但不会再次创建它:
@GetMapping("/destroyBean")
public void destroyBean() {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("ConfigManager");
}
这不会改变 Controller 已经持有的对 Bean 的引用。要访问最新状态,我们需要直接从 Context 中读取。
创建一个新 Controller 来读取配置。该 Controller 将依赖于 Context 中最新的 ConfigManager
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、总结 {#5总结}
在本文中,我们学习了重新初始化 Singleton Bean 的方法。我们介绍了一种在不重新创建 Bean 的情况下修改其属性的方法,以及强制在 Context 中重新创建 Bean 的方法。
参考:https://www.baeldung.com/spring-reinitialize-singleton-bean