Java Spring Recreate specific Bean

2022-09-02 21:57:00

我想在运行时重新创建(新对象)一个特定的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();
    }
}

此代码不会运行,因为对象是由Spring作为Singleton创建的,除非重新启动服务器,否则其上下文不会刷新。如何刷新(创建新对象)?myShopmyShop

我能想到的一个不好的方法是在里面创建新对象,但这对我来说似乎不对。myShoprestartListeners()


答案 1

在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,因为它已经从上下文中删除了,我想知道它是否仍然存在,或者如果在上面的控制器中发布它,如果上下文中的其他一些对象引用了它, 以上会导致问题。


答案 2

我们有相同的用例。如前所述,在运行时重新创建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;
    }
}

然后,我将豆子注射到需要的地方,例如

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() 调用以关闭它。但这不是这个例子想要展示的。


推荐