Безопасно ли начинать новый поток в управляемом компоненте 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();
}
Другие вопросы по тегам