Java Spring 重新创建特定的 Bean
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27998502/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Spring Recreate specific Bean
提问by Srikar Appalaraju
I want to re-create (new Object) a specific bean at Runtime (no restarting the server) upon some DB changes. This is how it looks -
我想在某些数据库更改时在运行时重新创建(新对象)特定 bean(不重新启动服务器)。这是它的样子——
@Component
public class TestClass {
@Autowired
private MyShop myShop; //to be refreshed at runtime bean
@PostConstruct //DB listeners
public void initializeListener() throws Exception {
//...
// code to get listeners config
//...
myShop.setListenersConfig(listenersConfig);
myShop.initialize();
}
public void restartListeners() {
myShop.shutdownListeners();
initializeListener();
}
}
This code does not run as myShop
object is created by Spring as Singleton & its context does not get refreshed unless the server is restarted. How to refresh (create a new object) myShop
?
此代码不会运行,因为myShop
对象是由 Spring 作为单例创建的,除非重新启动服务器,否则它的上下文不会刷新。如何刷新(创建新对象)myShop
?
One bad way I can think of is to create new myShop
object inside restartListeners()
but that does not seem right to me.
我能想到的一种不好的方法是在myShop
里面创建新对象,restartListeners()
但这对我来说似乎不对。
采纳答案by mariubog
In DefaultListableBeanFactory you have public method destroySingleton("beanName")so you can play with it, but you have to be aware that if your autowired your bean it will keep the same instance of the object that has been autowired in the first place, you can try something like this:
在 DefaultListableBeanFactory 中,您有公共方法 destroySingleton("beanName") 因此您可以使用它,但是您必须注意,如果您自动装配您的 bean,它将保留最初自动装配的对象的相同实例,您可以尝试这样的事情:
@RestController
public class MyRestController {
@Autowired
SampleBean sampleBean;
@Autowired
ApplicationContext context;
@Autowired
DefaultListableBeanFactory beanFactory;
@RequestMapping(value = "/ ")
@ResponseBody
public String showBean() throws Exception {
SampleBean contextBean = (SampleBean) context.getBean("sampleBean");
beanFactory.destroySingleton("sampleBean");
return "Compare beans " + sampleBean + "=="
+ contextBean;
//while sampleBean stays the same contextBean gets recreated in the context
}
}
It is not pretty but shows how you can approach it. If you were dealing with a controller rather than a component class, you could have an injection in method argument and it would also work, because Bean would not be recreated until needed inside the method, at least that's what it looks like. Interesting question would be who else has reference to the old Bean besides the object it has been autowired into in the first place,because it has been removed from the context, I wonder if it still exists or is garbage colected if released it in the controller above, if some other objects in the context had reference to it, above would cause problems.
它并不漂亮,但显示了您如何接近它。如果您正在处理控制器而不是组件类,则可以在方法参数中注入,它也可以工作,因为在方法内部需要之前不会重新创建 Bean,至少它看起来是这样。有趣的问题是,除了最初自动连接到的对象之外,还有谁引用了旧 Bean,因为它已从上下文中删除,我想知道它是否仍然存在,或者如果在控制器中释放它,它是否会被垃圾收集上面,如果上下文中的其他一些对象引用了它,上面会导致问题。
回答by Dan
We have the same use-case. As already mentioned one of the main issues with re-creating a bean during runtime is how to updating the references that have already been injected. This presents the main challenge.
我们有相同的用例。如前所述,在运行时重新创建 bean 的主要问题之一是如何更新已注入的引用。这是主要的挑战。
To work around this issue I've used Java's AtomicReference<> class. Instead of injecting the bean directly, I've wrapped it as an AtomicReference and then inject that. Because the object wrapped by the AtomicReference can be reset in a thread safe manner, I am able to use this to change the underlying object when a database change is detected. Below is an example config / usage of this pattern:
为了解决这个问题,我使用了 Java 的 AtomicReference<> 类。我没有直接注入 bean,而是将它包装为 AtomicReference,然后注入它。因为由 AtomicReference 包装的对象可以以线程安全的方式重置,所以当检测到数据库更改时,我可以使用它来更改底层对象。以下是此模式的示例配置/用法:
@Configuration
public class KafkaConfiguration {
private static final String KAFKA_SERVER_LIST = "kafka.server.list";
private static AtomicReference<String> serverList;
@Resource
MyService myService;
@PostConstruct
public void init() {
serverList = new AtomicReference<>(myService.getPropertyValue(KAFKA_SERVER_LIST));
}
// Just a helper method to check if the value for the server list has changed
// Not a big fan of the static usage but needed a way to compare the old / new values
public static boolean isRefreshNeeded() {
MyService service = Registry.getApplicationContext().getBean("myService", MyService.class);
String newServerList = service.getPropertyValue(KAFKA_SERVER_LIST);
// Arguably serverList does not need to be Atomic for this usage as this is executed
// on a single thread
if (!StringUtils.equals(serverList.get(), newServerList)) {
serverList.set(newServerList);
return true;
}
return false;
}
public ProducerFactory<String, String> kafkaProducerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.CLIENT_ID_CONFIG, "...");
// Here we are pulling the value for the serverList that has been set
// see the init() and isRefreshNeeded() methods above
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, serverList.get());
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
@Lazy
public AtomicReference<KafkaTemplate<String, String>> kafkaTemplate() {
KafkaTemplate<String, String> template = new KafkaTemplate<>(kafkaProducerFactory());
AtomicReference<KafkaTemplate<String, String>> ref = new AtomicReference<>(template);
return ref;
}
}
I then inject the bean where needed, e.g.
然后我在需要的地方注入 bean,例如
public MyClass1 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
public MyClass2 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
In a separate class I run a scheduler thread that is started when the application context is started. The class looks something like this:
在一个单独的类中,我运行一个调度程序线程,该线程在应用程序上下文启动时启动。这个类看起来像这样:
class Manager implements Runnable {
private ScheduledExecutorService scheduler;
public void start() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this, 0, 120, TimeUnit.SECONDS);
}
public void stop() {
scheduler.shutdownNow();
}
@Override
public void run() {
try {
if (KafkaConfiguration.isRefreshNeeded()) {
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate =
(AtomicReference<KafkaTemplate<String, String>>) Registry.getApplicationContext().getBean("kafkaTemplate");
// Get new instance here. This will have the new value for the server list
// that was "refreshed"
KafkaConfiguration config = new KafkaConfiguration();
// The set here replaces the wrapped objet in a thread safe manner with the new bean
// and thus all injected instances now use the newly created object
kafkaTemplate.set(config.kafkaTemplate().get());
}
} catch (Exception e){
} finally {
}
}
}
I am still on the fence if this is something I would advocate doing as it does have a slight smell to it. But in limited and careful usage it does provide an alternate approach to the stated use-case. Please be aware that from a Kafka standpoint this code example will leave the old producer open. In reality one would need to properly do a flush() call on the old producer to close it. But that's not what the example is meant to demonstrate.
如果这是我提倡做的事情,我仍然持观望态度,因为它确实有轻微的气味。但在有限且谨慎的使用中,它确实为所述用例提供了另一种方法。请注意,从 Kafka 的角度来看,此代码示例将使旧生产者保持打开状态。实际上,需要对旧生产者正确执行flush() 调用以关闭它。但这不是示例要演示的内容。