Czy można bezpiecznie założyć nowy wątek w JSF managed bean?
Nie mogłem znaleźć ostatecznej odpowiedzi na to, czy bezpieczne jest spawnowanie wątków w ramach zarządzanych fasoli JSF z zakresem sesji. Wątek musi wywoływać metody na bezpaństwowej instancji EJB (która została zaimplementowana do managed bean).
Tłem jest to, że mamy raport, który zajmuje dużo czasu, aby wygenerować. Spowodowało to opóźnienie żądania HTTP z powodu ustawień serwera, których nie możemy zmienić. Więc chodzi o to, aby rozpocząć nowy wątek i pozwolić mu wygenerować raport i tymczasowo przechowuj. W międzyczasie strona JSF pokazuje pasek postępu, sonduje zarządzaną fasolę aż do zakończenia generacji, a następnie wysyła drugi wniosek o pobranie zapisanego raportu. Wydaje się, że to działa, ale chciałbym się upewnić, że to, co robię, nie jest włamaniem.
3 answers
Wprowadzenie
Spawanie wątków z zakresu sesji managed bean niekoniecznie jest hakerem, o ile wykonuje zadanie, które chcesz. Ale tarło nici na własną rękę musi być wykonane ze szczególną ostrożnością. Kod nie powinien być napisany w taki sposób, aby pojedynczy użytkownik mógł na przykład wywołać nieograniczoną liczbę wątków na sesję i / lub aby wątki nadal działały nawet po zniszczeniu sesji. Prędzej czy później rozwaliłoby to Twoją aplikację.
The kod musi być napisany w ten sposób, aby użytkownik mógł na przykład nigdy nie wywoływać więcej niż jednego wątku w tle na sesję i aby wątek był gwarantowany, że zostanie przerwany za każdym razem, gdy sesja zostanie zniszczona. W przypadku wielu zadań w ramach sesji musisz ustawić kolejkę zadań.
Ponadto, wszystkie te wątki powinny być obsługiwane przez wspólną pulę wątków, aby można było ustalić limit całkowitej liczby zrodzonych wątków na poziomie aplikacji. Średnia Java EE serwer aplikacji oferuje pulę wątków zarządzanych kontenerem, którą można wykorzystać m.in. poprzez EJB @Asynchronous
oraz @Schedule
. Aby być niezależnym od kontenera, możesz również użyć Util w Javie 1.5ExecutorService
oraz ScheduledExecutorService
za to.
Poniżej przykłady zakładają Java EE 6 + z EJB.
Odpal i zapomnij o zadaniu na formularzu submit
@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() {
// ...
}
}
Asynchronicznie pobiera model podczas ładowania strony
@Named
@RequestScoped // Or @ViewScoped
public class Bean {
private Future<List<Entity>> asyncEntities;
@EJB
private EntityService entityService;
@PostConstruct
public void init() {
asyncEntities = entityService.asyncList();
// ... (this code will immediately continue without waiting)
}
public List<Entity> 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<List<Entity>> asyncList() {
List<Entity> entities = entityManager
.createQuery("SELECT e FROM Entity e", Entity.class)
.getResultList();
return new AsyncResult<>(entities);
}
}
In case you ' re korzystając z biblioteki JSF utility library OmniFaces , można to zrobić jeszcze szybciej, jeśli dodasz adnotację managed bean za pomocą @Eager
.
Zaplanuj zadania w tle podczas uruchamiania aplikacji
@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)
}
}
Ciągłe aktualizowanie modelu aplikacji w tle
@Named
@RequestScoped // Or @ViewScoped
public class Bean {
@EJB
private SomeTop100Manager someTop100Manager;
public List<Some> getSomeTop100() {
return someTop100Manager.list();
}
}
@Singleton
@ConcurrencyManagement(BEAN)
public class SomeTop100Manager {
@PersistenceContext
private EntityManager entityManager;
private List<Some> top100;
@PostConstruct
@Schedule(hour="*", minute="*/1", second="0", persistent=false)
public void load() {
top100 = entityManager
.createNamedQuery("Some.top100", Some.class)
.getResultList();
}
public List<Some> list() {
return top100;
}
}
Zobacz też:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:51
Sprawdź EJB 3.1 @Asynchronous methods
. Właśnie po to są.
Mały przykład, który używa OpenEJB 4.0.0-SNAPSHOTs. Tutaj mamy @Singleton
fasolę z jedną metodą oznaczoną @Asynchronous
. Za każdym razem, gdy ta metoda jest wywoływana przez kogokolwiek, w tym przypadku twój JSF managed bean, natychmiast powróci niezależnie od tego, jak długo metoda faktycznie trwa.
@Singleton
public class JobProcessor {
@Asynchronous
@Lock(READ)
@AccessTimeout(-1)
public Future<String> addJob(String jobName) {
// Pretend this job takes a while
doSomeHeavyLifting();
// Return our result
return new AsyncResult<String>(jobName);
}
private void doSomeHeavyLifting() {
try {
Thread.sleep(SECONDS.toMillis(10));
} catch (InterruptedException e) {
Thread.interrupted();
throw new IllegalStateException(e);
}
}
}
Oto mały test, który wywołuje tę metodę kilka razy z rzędu.
Każde wywołanie zwraca Future obiekt, który zasadniczo zaczyna się empty, a później jego wartość zostanie wypełniona przez kontener, gdy powiązane wywołanie metody zakończy się.
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<String> red = processor.addJob("red");
final Future<String> orange = processor.addJob("orange");
final Future<String> yellow = processor.addJob("yellow");
final Future<String> green = processor.addJob("green");
final Future<String> blue = processor.addJob("blue");
final Future<String> 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);
}
}
Pod pokrywami co sprawia, że ta praca jest:
-
JobProcessor
widziany przez rozmówcę nie jest w rzeczywistości instancjąJobProcessor
. Jest to raczej podklasa lub proxy, która ma nadpisane wszystkie metody. Obsługiwane są metody, które mają być asynchroniczne inaczej. - wywołania metody asynchronicznej po prostu powodują utworzenie
Runnable
, która zawija podaną metodę i parametry. Ta funkcja jest przekazywana do executora, który jest po prostu kolejką roboczą dołączoną do puli wątków. - Po dodaniu pracy do kolejki, wersja Proxy metody zwraca implementację
Future
, która jest powiązana zRunnable
, która teraz czeka w kolejce. - kiedy
Runnable
W końcu wykona metodę na realJobProcessor
instancja pobierze wartość zwracaną i ustawi ją wFuture
, udostępniając ją wywołującemu.
Należy pamiętać, że obiekt AsyncResult
, który zwraca JobProcessor
, nie jest tym samym obiektem Future
, który przechowuje wywołujący. Byłoby fajnie, gdyby prawdziwa {[6] } mogła po prostu powrócić String
, a wersja wywołująca JobProcessor
mogła powrócić Future<String>
, ale nie widzieliśmy żadnego sposobu, aby to zrobić bez dodawania większej złożoności. Więc {[14] } jest prostym obiektem wrappera. Na pojemnik wyciągnie String
, wyrzuci AsyncResult
, a następnie umieści String
w rzeczywistym Future
że rozmówca trzyma.
Aby uzyskać postęp po drodze, po prostu przekaż obiekt bezpieczny dla wątku, taki jak AtomicInteger do metody @Asynchronous
i poproś, aby Kod bean okresowo aktualizował go z procentami.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-04 23:27:07
Próbowałem tego i działa świetnie z mojego JSF managed bean
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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-18 02:46:08