2015-01-17 20 views
11

Ich möchte eine bestimmte Bean zur Laufzeit (kein Neustart des Servers) bei einigen DB-Änderungen neu erstellen (neues Objekt). Dies ist, wie es aussieht -Java Spring Recreate spezifische 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(); 
    } 
} 

Dieser Code läuft nicht als myShop Objekt von Spring als Singleton & sein Kontext erhält nicht aufgefrischt erstellt wird, wenn der Server neu gestartet wird. Wie aktualisiere ich (ein neues Objekt erstellen) myShop?

Eine schlechte Möglichkeit, die ich mir vorstellen kann, ist, neue myShop Objekt innerhalb restartListeners() zu schaffen, aber das scheint nicht richtig zu mir.

+0

Die Eigenschaften von Beans werden in der BeanFactory Post Processing-Phase geladen, und ich glaube nicht, dass wir in der Lage sind, danach viel zu tun, besonders in Singletons, Sie müssten den App-Kontext wiederherstellen, in den die Beans geladen werden. Hier ein interessantes Beispiel: http://stackoverflow.com/questions/4084890/spring-replacing-the-bean-property-values-with-new-property-file-values – mariubog

Antwort

4

In der DefaultListableBeanFactory haben Sie die public Methode destroySingleton ("beanName"), damit Sie damit spielen können, aber Sie müssen wissen, dass wenn Sie Ihre Bean autowired haben, dieselbe Instanz des Objekts, das in der Autowired wurde Erstens, können Sie so etwas wie dies versuchen:

@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 
      } 

    } 

es ist nicht schön, aber zeigt, wie man es erreichen kann. Wenn Sie mit einem Controller und nicht mit einer Komponentenklasse arbeiten, könnten Sie eine Injektion in method argument haben, und es würde auch funktionieren, weil Bean nicht neu erstellt wird, bis es in der Methode benötigt wird, zumindest sieht es so aus. Interessante Frage wäre, wer sonst noch Bezug auf die alte Bean hat, abgesehen von dem Objekt, in das es ursprünglich hineingewagt wurde, weil es aus dem Kontext entfernt wurde, frage ich mich, ob es noch existiert oder Müll ist, wenn es im Controller freigegeben wird oben, wenn einige andere Objekte im Kontext darauf verweisen, würde oben Probleme verursachen.

1

Wir haben den gleichen Anwendungsfall. Wie bereits erwähnt, besteht eines der Hauptprobleme bei der Neuerstellung einer Bean während der Laufzeit darin, die Referenzen zu aktualisieren, die bereits injiziert wurden. Dies ist die größte Herausforderung.

Um dieses Problem zu umgehen, habe ich die AtomicReference-Klasse <> von Java verwendet. Anstatt die Bean direkt zu injizieren, habe ich sie als AtomicReference umschlossen und dann injiziert. Da das von der AtomicReference umschlossene Objekt threadsicher zurückgesetzt werden kann, kann ich das zugrunde liegende Objekt ändern, wenn eine Datenbankänderung erkannt wird. Unten ist ein Beispiel einer Konfiguration/Verwendung dieses Musters:

Ich injiziere dann die Bohne wo erforderlich, z.B.

In einer separaten Klasse ich einen Scheduler-Thread ausführen, der beim Start des Anwendungskontexts gestartet wird. Die Klasse sieht ungefähr so ​​aus:

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 { 

     } 
    } 
} 

ich immer noch auf dem Zaun bin, wenn dies etwas, das ich tun befürworten würde, wie es einen leichten Geruch es hat. Bei eingeschränkter und sorgfältiger Verwendung bietet es jedoch einen alternativen Ansatz für den angegebenen Anwendungsfall. Bitte beachten Sie, dass dieses Codebeispiel den alten Hersteller aus Kafka-Sicht offen lässt. In Wirklichkeit müsste man einen flush() Aufruf an den alten Produzenten machen, um ihn zu schließen. Aber das soll das Beispiel nicht zeigen.