Sri*_*aju 15 java singleton spring javabeans
我想在运行时重新创建(新对象)特定的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();
}
}
Run Code Online (Sandbox Code Playgroud)
此代码不会运行,因为myShop
Spring将作为Singleton创建对象,并且除非重新启动服务器,否则它的上下文不会刷新.如何刷新(创建一个新对象)myShop
?
我能想到的一个坏方法是在myShop
里面创建新对象,restartListeners()
但这对我来说似乎并不合适.
在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
}
}
Run Code Online (Sandbox Code Playgroud)
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 的主要问题之一是如何更新已注入的引用。这是主要的挑战。
为了解决这个问题,我使用了 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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我在需要的地方注入 bean,例如
public MyClass1 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
public MyClass2 {
@Resource
AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
...
}
Run Code Online (Sandbox Code Playgroud)
在一个单独的类中,我运行一个在应用程序上下文启动时启动的调度程序线程。这个类看起来像这样:
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 {
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果这是我提倡做的事情,我仍然持观望态度,因为它确实有轻微的气味。但在有限且谨慎的使用中,它确实为所述用例提供了另一种方法。请注意,从 Kafka 的角度来看,此代码示例将使旧生产者保持打开状态。实际上,需要对旧生产者正确执行 flush() 调用以关闭它。但这不是示例要演示的内容。
归档时间: |
|
查看次数: |
15190 次 |
最近记录: |