web-dev-qa-db-ja.com

Java Spring特定のBeanを再作成

いくつかのDBの変更時に、実行時に特定の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

私が考えることができる1つの悪い方法は、restartListeners()内に新しいmyShopオブジェクトを作成することですが、それは私には正しくないようです。

15

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を誰が参照しているかです。それはコンテキストから削除されているため、それがまだ存在するか、コントローラーで解放された場合にガベージコレクションされているのでしょうか。上記では、コンテキスト内の他のオブジェクトがそれを参照している場合、上記で問題が発生します。

8
mariubog

同じユースケースがあります。すでに述べたように、実行時にBeanを再作成する場合の主な問題の1つは、すでに注入されている参照を更新する方法です。これが主な課題です。

この問題を回避するために、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()呼び出しを適切に実行して閉じる必要があります。しかし、それはこの例が示すものではありません。

4
Dan