Безопасно ли начинать новый поток в управляемом компоненте JSF? - PullRequest
44 голосов
/ 27 мая 2011

Я не смог найти однозначного ответа на вопрос, безопасно ли порождать потоки в управляемых бинах JSF в сессионной области.Поток должен вызывать методы для экземпляра EJB без сохранения состояния (который был внедрен зависимостью в управляемый bean-компонент).

Фоном является то, что у нас есть отчет, генерация которого занимает много времени.Это вызвало тайм-аут запроса HTTP из-за настроек сервера, которые мы не можем изменить.Таким образом, идея состоит в том, чтобы начать новый поток и позволить ему создать отчет и временно сохранить его.Тем временем на странице JSF отображается индикатор выполнения, опрашивает управляемый компонент до завершения генерации, а затем делает второй запрос на загрузку сохраненного отчета.Кажется, это работает, но я хотел бы убедиться, что то, что я делаю, не является хаком.

Ответы [ 3 ]

52 голосов
/ 28 мая 2011

Проверьте 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, который оборачивает метод и параметры, которые вы задали.Этот исполняемый объект передается Executor , который представляет собой просто рабочую очередь, присоединенную к пулу потоков.
  • После добавления работы в очередь прокси-версия метода возвращает реализацию Future, который связан с Runnable, который сейчас ожидает в очереди.
  • Когда Runnable наконец выполнит метод для экземпляра real JobProcessor, он будетвозвращаемое значение и установите его в Future, делая его доступным для вызывающей стороны.

Важно отметить, что объект AsyncResult, возвращаемый JobProcessor, не является тем же объектом Futureзвонящий держит.Было бы замечательно, если бы реальный JobProcessor мог просто вернуть String, а версия вызывающего абонента JobProcessor могла бы вернуть Future<String>, но мы не видели никакого способа сделать это, не добавив больше сложности.Таким образом, AsyncResult - это простой объект-обертка.Контейнер вытянет String, выбросит AsyncResult, затем поместит String в real Future, который удерживает вызывающий.

Чтобы получить прогресспопутно просто передайте потокобезопасный объект, такой как AtomicInteger , методу @Asynchronous и попросите код бина периодически обновлять его до процента завершения.

42 голосов
/ 27 мая 2011

Введение

Создание потоков из управляемого bean-объекта области сеанса не обязательно является хаком, если оно выполняет ту работу, которую вы хотите. Но порождение нитей само по себе должно быть сделано с особой осторожностью. Код не должен быть написан таким образом, чтобы один пользователь мог, например, создавать неограниченное количество потоков в сеансе и / или чтобы потоки продолжали работать даже после того, как сеанс был разрушен. Рано или поздно оно взорвёт вашу заявку.

Код должен быть написан таким образом, чтобы вы могли гарантировать, что пользователь, например, никогда не сможет создавать более одного фонового потока за сеанс и что поток гарантированно будет прерван при каждом разрушении сеанса. Для выполнения нескольких задач в течение одного сеанса необходимо поставить их в очередь.

Кроме того, все эти потоки должны предпочтительно обслуживаться общим пулом потоков, чтобы можно было ограничить общее количество порождаемых потоков на уровне приложения. Средний сервер приложений Java EE предлагает управляемый контейнером пул потоков, который вы можете использовать, среди прочего, в EJB @Asynchronous и @Schedule. Чтобы быть независимым от контейнера, вы также можете использовать для этого Java 1.5 Util Concurrent 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;
    }

}

Смотри также:

0 голосов
/ 18 августа 2015

Я попробовал это и отлично работает с моим управляемым компонентом 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();
}
...