Безопасно ли начинать новый поток в управляемом компоненте JSF?
Я не смог найти однозначного ответа на вопрос о том, безопасно ли порождать потоки внутри управляемых JSF-бинов в рамках сеанса. Поток должен вызывать методы для экземпляра EJB без сохранения состояния (который был внедрен зависимостью в управляемый компонент).
Фон в том, что у нас есть отчет, который занимает много времени для генерации. Это вызвало тайм-аут запроса HTTP из-за настроек сервера, которые мы не можем изменить. Таким образом, идея состоит в том, чтобы начать новый поток и позволить ему сгенерировать отчет и временно сохранить его. Тем временем на странице JSF отображается индикатор выполнения, опрашивает управляемый компонент до завершения генерации, а затем выполняет второй запрос на загрузку сохраненного отчета. Кажется, это работает, но я хотел бы убедиться, что то, что я делаю, не является хаком.
3 ответа
Вступление
Создание потоков в управляемом bean-объекте bean-объекта не обязательно является хаком, если он выполняет свою работу. Но порождение нитей само по себе должно быть сделано с особой осторожностью. Код не должен быть написан таким образом, чтобы один пользователь мог, например, создавать неограниченное количество потоков в сеансе и / или чтобы потоки продолжали работать даже после того, как сеанс был разрушен. Это взорвало бы ваше заявление рано или поздно.
Код должен быть написан таким образом, чтобы вы могли гарантировать, что пользователь, например, никогда не сможет создавать более одного фонового потока за сеанс и что поток гарантированно будет прерван при каждом разрушении сеанса. Для выполнения нескольких задач в течение одного сеанса необходимо поставить их в очередь.
Кроме того, все эти потоки должны предпочтительно обслуживаться общим пулом потоков, чтобы можно было ограничить общее количество порождаемых потоков на уровне приложения. Средний сервер приложений Java EE предлагает управляемый контейнером пул потоков, который вы можете использовать среди других EJB @Asynchronous
а также @Schedule
, Чтобы быть независимым от контейнера, вы также можете использовать Util Concurrent в Java 1.5 ExecutorService
а также ScheduledExecutorService
за это.
Ниже приведены примеры использования Java EE 6+ с EJB.
Запустить и забыть задание при отправке формы
@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() {
// ...
}
}
Асинхронное получение модели при загрузке страницы
@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);
}
}
Если вы используете служебную библиотеку JSF OmniFaces, это можно сделать еще быстрее, если вы аннотируете управляемый компонент с помощью @Eager
,
Планирование фоновых заданий при запуске приложения
@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)
}
}
Постоянно обновлять модель приложения в фоновом режиме
@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;
}
}
Смотрите также:
Проверьте EJB 3.1 @Asynchronous methods
, Это именно то, для чего они.
Небольшой пример, использующий OpenEJB 4.0.0-SNAPSHOT. Здесь у нас есть @Singleton
боб с одним отмеченным методом @Asynchronous
, Каждый раз, когда этот метод вызывается кем-либо, в этом случае ваш управляемый JSF-компонент будет немедленно возвращаться независимо от того, сколько времени на самом деле занимает метод.
@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);
}
}
}
Вот небольшой тестовый пример, который вызывает @Asynchronous
метод несколько раз подряд.
Каждый вызов возвращает объект Future, который по существу начинается пустым, и позднее его значение будет заполнено контейнером, когда вызов связанного метода фактически завершится.
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);
}
}
Под покровом то, что делает эту работу:
JobProcessor
вызывающий абонент на самом деле не является экземпляромJobProcessor
, Скорее это подкласс или прокси, у которого все методы переопределены. Методы, которые должны быть асинхронными, обрабатываются по-разному.- Вызов асинхронного метода просто приводит к
Runnable
создается, что оборачивает метод и параметры, которые вы дали. Этот исполняемый файл передается исполнителю, который является просто рабочей очередью, присоединенной к пулу потоков. - После добавления работы в очередь прокси-версия метода возвращает реализацию
Future
это связано сRunnable
который сейчас ждет в очереди. - Когда
Runnable
наконец выполняет метод на реальномJobProcessor
Например, он примет возвращаемое значение и установит его вFuture
сделать его доступным для звонящего.
Важно отметить, что AsyncResult
возражать JobProcessor
возвращается не то же самое Future
объект, который держит вызывающий. Было бы здорово, если бы реальный JobProcessor
мог просто вернуться String
и версия звонящего JobProcessor
мог вернуться Future<String>
, но мы не видели никакого способа сделать это, не добавляя больше сложности. Итак AsyncResult
это простой объект-обертка Контейнер будет тянуть String
выкинуть AsyncResult
прочь, затем положи String
в реале Future
что звонящий держит.
Чтобы добиться прогресса, просто передайте потокобезопасный объект, такой как AtomicInteger, @Asynchronous
метод и имейте код компонента, периодически обновляйте его с завершенным процентом.
Я попробовал это и отлично работает из моего управляемого 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();
}