2015-01-17 22 views
11

Voglio ricreare (nuovo oggetto) un bean specifico in fase di runtime (senza riavviare il server) su alcune modifiche del DB. Questo è come appare -Java Spring Recreate Bean specifico

@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(); 
    } 
} 

Questo codice non viene eseguito come myShop oggetto viene creato entro la primavera come Singleton & il suo contesto non ottiene aggiornata a meno che si riavvia il server. Come aggiornare (creare un nuovo oggetto) myShop?

Un modo sbagliato che posso pensare è creare un nuovo oggetto myShop all'interno di restartListeners() ma ciò non mi sembra giusto.

+0

le proprietà dei bean vengono caricate nella fase di post processing di Bean Factory e non penso che saremo in grado di fare molto dopo di ciò in termini di ricaricamento specialmente nei singleton, dovresti ricostruire il contesto dell'app in cui sono caricati i bean, ecco un esempio interessante, http://stackoverflow.com/questions/4084890/spring-replacing-the-bean-property-values-with-new-property-file-values – mariubog

risposta

4

In DefaultListableBeanFactory avete il metodo public destroySingleton ("beanName") in modo da poter giocare con esso, ma dovete essere consapevoli che se il vostro bean auto-attivato manterrà la stessa istanza dell'oggetto che è stato autowired nel primo posto, puoi provare qualcosa del genere:

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

    } 

Non è bello ma mostra come ci si può avvicinare. Se avessi a che fare con un controller piuttosto che con una classe componente, potresti avere un'iniezione nell'argomento metodo e funzionerebbe anche, perché Bean non sarebbe ricreato fino a quando non fosse necessario all'interno del metodo, almeno questo è ciò che sembra. Interessante domanda: chi altri ha fatto riferimento al vecchio fagiolo a parte l'oggetto in cui è stato avviato automaticamente, perché è stato rimosso dal contesto, mi chiedo se esista ancora o venga spazzato via se rilasciato nel controller sopra, se qualche altro oggetto nel contesto avesse riferimento ad esso, sopra causerebbe problemi.

1

Abbiamo lo stesso caso d'uso. Come già accennato, uno dei problemi principali con la ricreazione di un bean durante il runtime è come aggiornare i riferimenti che sono già stati iniettati. Questo presenta la sfida principale.

Per risolvere questo problema, ho utilizzato la classe Java AtomicReference <>. Invece di iniettare direttamente il bean, l'ho avvolto come AtomicReference e poi lo inietto. Poiché l'oggetto racchiuso da AtomicReference può essere ripristinato in modo thread-safe, sono in grado di utilizzarlo per modificare l'oggetto sottostante quando viene rilevata una modifica del database. Di seguito è riportato un esempio di configurazione/utilizzo di questo modello:

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

I quindi iniettare il bean dove necessario, ad es.

In una classe separata, eseguo un thread dello scheduler che viene avviato all'avvio del contesto dell'applicazione. La classe di simile a questa:

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 { 

     } 
    } 
} 

io sono ancora sul recinto, se questo è qualcosa che vorrei sostenere farlo in quanto ha un leggero odore. Ma in un uso limitato e attento fornisce un approccio alternativo al caso d'uso dichiarato. Si prega di notare che da un punto di vista di Kafka questo esempio di codice lascerà aperto il vecchio produttore. In realtà bisognerebbe effettuare correttamente una chiamata a filo() sul vecchio produttore per chiuderla. Ma non è quello che l'esempio intende dimostrare.