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.

Author: Dmitry Chornyi, 2011-05-27

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ż:

 42
Author: BalusC,
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);
    }
}

Przykładowy kod źródłowy

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 z Runnable, która teraz czeka w kolejce.
  • kiedy Runnable W końcu wykona metodę na real JobProcessor instancja pobierze wartość zwracaną i ustawi ją w Future, 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.

 52
Author: David Blevins,
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();
}
 -4
Author: Sacky,
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