我想在运行时(在没有重新启动服务器的情况下)在某些数据库更改时重新创建(新对象)特定的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();
}
}
此代码不会运行,因为myShop
对象是由Spring作为Singleton& amp;创建的。除非重新启动服务器,否则不会刷新其上下文。如何刷新(创建新对象)myShop
?
我能想到的一个不好的方法是在myShop
内创建新的restartListeners()
对象,但这对我来说似乎不对。
答案 0 :(得分:7)
在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
}
}
它不漂亮,但展示了如何接近它。如果你正在处理一个控制器而不是一个组件类,你可以在方法参数中进行注入,它也可以工作,因为在方法内部需要之前不会重新创建Bean,至少它就是它的样子。有趣的问题是除了首先被自动装入的对象之外还有谁引用了旧Bean,因为它已从上下文中删除,我想知道它是否仍然存在或者如果在控制器中释放它是垃圾收集的上面,如果上下文中的某些其他对象引用了它,上面会引起问题。
答案 1 :(得分:4)
我们有相同的用例。如前所述,在运行时重新创建bean的主要问题之一是如何更新已经注入的引用。这是主要的挑战。
要解决此问题,我使用了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;
}
}
然后我在需要的地方注入bean,例如
public MyClass1 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
public MyClass2 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
在一个单独的类中,我运行一个在启动应用程序上下文时启动的调度程序线程。该课程看起来像这样:
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 {
}
}
}
如果这是我主张要做的事情,我仍然在罢工,因为它确实有一点点气味。但是,在有限和谨慎的使用中,它确实为所述用例提供了另一种方法。请注意,从Kafka的角度来看,这个代码示例将使旧的生产者保持开放状态。实际上,需要在旧生产者上正确执行flush()调用以关闭它。但这不是示例的意思。