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接口时,将会获取到最新的配置。