セッションスコープのJSFマネージドBean内でスレッドを生成しても安全かどうかについて、明確な答えは見つかりませんでした。スレッドは、ステートレスEJBインスタンス(マネージドBeanに依存性が注入された)のメソッドを呼び出す必要があります。
背景は、生成に時間がかかるレポートがあることです。これにより、サーバーの設定を変更できないため、HTTPリクエストがタイムアウトしました。そのため、新しいスレッドを開始してレポートを生成させ、一時的に保存するという考えです。その間、JSFページには進行状況バーが表示され、生成が完了するまで管理Beanをポーリングしてから、保存されているレポートをダウンロードするための2番目の要求を行います。これはうまくいくようですが、私がやっていることはハックではないことを確認したいと思います。
セッションスコープのマネージドBean内からスレッドを生成することは、必要な作業を行う限り、必ずしもハックではありません。ただし、スレッドの生成自体は、細心の注意を払って行う必要があります。たとえば、1人のユーザーがセッションごとに無制限のスレッドを生成したり、セッションが破棄された後でもスレッドが実行を継続したりするような方法でコードを記述しないでください。それは遅かれ早かれあなたのアプリケーションを爆破するでしょう。
たとえば、ユーザーがセッションごとに複数のバックグラウンドスレッドを決して発生させないようにし、セッションが破棄されるたびにスレッドが中断されることが保証されるように、コードをこのように記述する必要があります。セッション内の複数のタスクについては、タスクをキューに入れる必要があります。また、アプリケーションレベルで生成されるスレッドの合計量に制限を設けることができるように、これらのスレッドはすべて、共通のスレッドプールによって提供されることが望ましいです。
したがって、スレッドの管理は非常にデリケートな作業です。そのため、new Thread()
やフレンドを使用して独自に自作するのではなく、組み込みの機能を使用する方がよいでしょう。平均Java EEアプリケーションサーバーは、EJBの中で利用できるコンテナー管理スレッドプールを提供します @Asynchronous
および @Schedule
。コンテナに依存しないようにするには(読み:Tomcatに対応)、Java 1.5のUtil Concurrent ExecutorService
および ScheduledExecutorService
これには。
以下の例では、Java EE 6+ with 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 を使用している場合、マネージドBeanに @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
Beanがあり、@Asynchronous
とマークされた1つのメソッドがあります。そのメソッドがだれか(この場合はJSF管理対象Bean)によって呼び出されるたびに、メソッドが実際にかかる時間に関係なく、すぐに戻ります。
@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 オブジェクトを返します。これは基本的にemptyで始まり、後で関連するときにコンテナによって値が入力されますメソッド呼び出しは実際に完了します。
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
がrealJobProcessor
インスタンスで最後にメソッドを実行すると、戻り値を取得して設定しますFuture
に入れて、呼び出し元が利用できるようにします。AsyncResult
が返すJobProcessor
オブジェクトは、呼び出し元が保持しているFuture
オブジェクトとは異なることに注意してください。実際のJobProcessor
がString
を返すだけで、呼び出し元のJobProcessor
がFuture<String>
を返すことができれば、それはすばらしいことでしたが、方法はわかりませんでした複雑さを増すことなくそれを行うために。したがって、AsyncResult
は単純なラッパーオブジェクトです。コンテナはString
を引き出し、AsyncResult
を捨て、String
をrealFuture
呼び出し元が保持していること。
途中で進捗状況を取得するには、単純に AtomicInteger のようなスレッドセーフオブジェクトを@Asynchronous
メソッドに渡し、Beanコードで完了率を定期的に更新します。