È sicuro iniziare un nuovo thread in un bean gestito JSF?

Non sono riuscito a trovare una risposta definitiva al fatto che sia sicuro generare i thread all’interno dei bean gestiti JSF con scope di sessione. Il thread deve chiamare i metodi sull’istanza EJB stateless (che è stata inserita in dipendenza dal bean gestito).

Lo sfondo è che abbiamo un rapporto che richiede molto tempo per generare. Ciò ha causato il timeout della richiesta HTTP a causa delle impostazioni del server che non possiamo modificare. Quindi l’idea è di iniziare un nuovo thread e lasciarlo generare il report e memorizzarlo temporaneamente. Nel frattempo la pagina JSF mostra una barra di avanzamento, esegue il polling del bean gestito fino al completamento della generazione e quindi effettua una seconda richiesta per scaricare il report memorizzato. Questo sembra funzionare, ma mi piacerebbe essere sicuro di quello che sto facendo non è un hack.

introduzione

I thread di spawning all’interno di un bean gestito con scope di sessione non sono necessariamente un hack fintanto che fanno il lavoro che si desidera. Ma generare i thread alle proprie esigenze deve essere fatto con estrema cura. Il codice non dovrebbe essere scritto in questo modo che un singolo utente possa generare una quantità illimitata di thread per sessione e / o che i thread continuino a funzionare anche dopo che la sessione è stata distrutta. Farà esplodere la tua applicazione prima o poi.

Il codice deve essere scritto in questo modo che è ansible garantire che un utente possa, ad esempio, non generare mai più thread in background per sessione e che il thread sia garantito per essere interrotto ogni volta che la sessione viene distrutta. Per più attività all’interno di una sessione è necessario accodare le attività.

Inoltre, tutti questi thread dovrebbero essere preferibilmente serviti da un pool di thread comune in modo da poter limitare la quantità totale di thread generati a livello di applicazione. Il server delle applicazioni Java EE medio offre un pool di thread gestito dal contenitore che è ansible utilizzare tra gli altri EJB’s @Asynchronous e @Schedule . Per essere indipendente dal contenitore, è ansible utilizzare anche Util ConcurrentService di Java 1.5 e ScheduledExecutorService per questo.

Sotto gli esempi si presume Java EE 6+ con EJB.

Fai fuoco e dimentica un’attività sul modulo di invio

 @Named @RequestScoped // Or @ViewScoped public class Bean { @EJB private SomeService someService; public void submit() { someService.asyncTask(); // ... (this code will immediately continue without waiting) } } 
 @Stateless public class SomeService { @Asynchronous public void asyncTask() { // ... } } 

Recupera in modo asincrono il modello al caricamento della pagina

 @Named @RequestScoped // Or @ViewScoped public class Bean { private Future> asyncEntities; @EJB private EntityService entityService; @PostConstruct public void init() { asyncEntities = entityService.asyncList(); // ... (this code will immediately continue without waiting) } public List getEntities() { try { return asyncEntities.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new FacesException(e); } catch (ExecutionException e) { throw new FacesException(e); } } } 
 @Stateless public class EntityService { @PersistenceContext private EntityManager entityManager; @Asynchronous public Future> asyncList() { List entities = entityManager .createQuery("SELECT e FROM Entity e", Entity.class) .getResultList(); return new AsyncResult<>(entities); } } 

Nel caso si stia utilizzando la libreria di utilità JSF OmniFaces , questa operazione potrebbe essere eseguita ancora più velocemente se si annota il bean gestito con @Eager .

Pianifica i processi in background all’avvio dell’applicazione

 @Singleton public class BackgroundJobManager { @Schedule(hour="0", minute="0", second="0", persistent=false) public void someDailyJob() { // ... (runs every start of day) } @Schedule(hour="*/1", minute="0", second="0", persistent=false) public void someHourlyJob() { // ... (runs every hour of day) } @Schedule(hour="*", minute="*/15", second="0", persistent=false) public void someQuarterlyJob() { // ... (runs every 15th minute of hour) } @Schedule(hour="*", minute="*", second="*/30", persistent=false) public void someHalfminutelyJob() { // ... (runs every 30th second of minute) } } 

Aggiorna continuamente il modello a livello di applicazione in background

 @Named @RequestScoped // Or @ViewScoped public class Bean { @EJB private SomeTop100Manager someTop100Manager; public List getSomeTop100() { return someTop100Manager.list(); } } 
 @Singleton @ConcurrencyManagement(BEAN) public class SomeTop100Manager { @PersistenceContext private EntityManager entityManager; private List top100; @PostConstruct @Schedule(hour="*", minute="*/1", second="0", persistent=false) public void load() { top100 = entityManager .createNamedQuery("Some.top100", Some.class) .getResultList(); } public List list() { return top100; } } 

Guarda anche:

  • Creazione di thread in un bean gestito JSF per attività pianificate utilizzando un timer

Controlla i @Asynchronous methods EJB 3.1 @Asynchronous methods . Questo è esattamente ciò per cui sono.

Piccolo esempio che utilizza OpenEJB 4.0.0-SNAPSHOTs. Qui abbiamo un bean @Singleton con un metodo contrassegnato da @Asynchronous . Ogni volta che il metodo viene invocato da chiunque, in questo caso il bean gestito da JSF, verrà immediatamente restituito indipendentemente dalla durata effettiva del metodo.

 @Singleton public class JobProcessor { @Asynchronous @Lock(READ) @AccessTimeout(-1) public Future addJob(String jobName) { // Pretend this job takes a while doSomeHeavyLifting(); // Return our result return new AsyncResult(jobName); } private void doSomeHeavyLifting() { try { Thread.sleep(SECONDS.toMillis(10)); } catch (InterruptedException e) { Thread.interrupted(); throw new IllegalStateException(e); } } } 

Ecco una piccola testina che richiama il metodo @Asynchronous più volte di seguito.

Ogni invocazione restituisce un object Future che essenzialmente inizia vuoto e in seguito avrà il suo valore compilato dal contenitore quando la chiamata al metodo correlato viene effettivamente completata.

 import javax.ejb.embeddable.EJBContainer; import javax.naming.Context; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class JobProcessorTest extends TestCase { public void test() throws Exception { final Context context = EJBContainer.createEJBContainer().getContext(); final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor"); final long start = System.nanoTime(); // Queue up a bunch of work final Future red = processor.addJob("red"); final Future orange = processor.addJob("orange"); final Future yellow = processor.addJob("yellow"); final Future green = processor.addJob("green"); final Future blue = processor.addJob("blue"); final Future violet = processor.addJob("violet"); // Wait for the result -- 1 minute worth of work assertEquals("blue", blue.get()); assertEquals("orange", orange.get()); assertEquals("green", green.get()); assertEquals("red", red.get()); assertEquals("yellow", yellow.get()); assertEquals("violet", violet.get()); // How long did it take? final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start); // Execution should be around 9 - 21 seconds assertTrue("" + total, total > 9); assertTrue("" + total, total < 21); } } 

Esempio di codice sorgente

Sotto le coperte ciò che rende questo lavoro è:

  • Il JobProcessor vede il chiamante non è in realtà un'istanza di JobProcessor . Piuttosto è una sottoclass o un proxy che ha tutti i metodi sovrascritti. I metodi che dovrebbero essere asincroni vengono gestiti in modo diverso.
  • Le chiamate a un metodo asincrono comportano semplicemente la creazione di un Runnable che racchiude il metodo ei parametri che hai fornito. Questo runnable è dato a un Executor che è semplicemente una coda di lavoro collegata a un pool di thread.
  • Dopo aver aggiunto il lavoro alla coda, la versione con proxy del metodo restituisce un'implementazione di Future che è collegata al Runnable che è ora in attesa sulla coda.
  • Quando infine il Runnable esegue il metodo JobProcessor reale , prenderà il valore di ritorno e lo imposterà nel Future rendendolo disponibile al chiamante.

È importante notare che l'object JobProcessor restituito da JobProcessor non è lo stesso object Future che il chiamante sta tenendo. Sarebbe stato bello se il vero JobProcessor fosse in grado di restituire String e la versione del JobProcessor del JobProcessor potesse restituire Future , ma non abbiamo visto alcun modo per farlo senza aggiungere più complessità. Quindi AsyncResult è un semplice object wrapper. Il contenitore AsyncResult la AsyncResult , rimuoverà AsyncResult , quindi AsyncResult la String nel Future reale in cui si trova il chiamante.

Per ottenere progressi lungo il percorso, è sufficiente passare un object thread-safe come AtomicInteger al metodo @Asynchronous e aggiornare periodicamente il codice bean con la percentuale di completamento.

Ho provato questo e funziona benissimo dal mio bean gestito JSF

 ExecutorService executor = Executors.newFixedThreadPool(1); @EJB private IMaterialSvc materialSvc; private void updateMaterial(Material material, String status, Location position) { executor.execute(new Runnable() { public void run() { synchronized (position) { // TODO update material in audit? do we need materials in audit? int index = position.getMaterials().indexOf(material); Material m = materialSvc.getById(material.getId()); m.setStatus(status); m = materialSvc.update(m); if (index != -1) { position.getMaterials().set(index, m); } } } }); } @PreDestroy public void destory() { executor.shutdown(); }