Java Spring 重新创建特定 Bean

Java Spring Recreate specific Bean

我想在某些数据库更改时在运行时重新创建(新对象)特定 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 作为单例创建的,除非重新启动服务器,否则不会刷新其上下文。如何刷新(创建新对象)myShop ?

我能想到的一个糟糕的方法是在 restartListeners() 中创建新的 myShop 对象,但这对我来说似乎不正确。

在 DefaultListableBeanFactory 中你有 public 方法 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
            }

    }

它不是很漂亮,但展示了如何处理它。如果你正在处理一个控制器而不是一个组件class,你可以在方法参数中进行注入,它也可以工作,因为在方法内部需要之前 Bean 不会被重新创建,至少它看起来是这样的.有趣的问题是,除了它首先被自动装配到的对象之外,还有谁引用了旧 Bean,因为它已经从上下文中删除,我想知道它是否仍然存在或者如果在控制器中释放它是否被垃圾收集上面,如果上下文中的其他一些对象引用了它,上面会导致问题。

我们有相同的用例。正如已经提到的,在 运行 期间重新创建 bean 的主要问题之一是如何更新已经注入的引用。这是主要的挑战。

为了解决这个问题,我使用了 Java 的 AtomicReference<> class。我没有直接注入 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 我 运行 一个调度程序线程,它在应用程序上下文启动时启动。 class 看起来像这样:

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() 调用以关闭它。但这不是示例要演示的内容。