多かれ少なかれ同じ問題がありましたが、別のアプローチを取りました@ConversationScoped
EntityManager
。リポジトリに注入しましたが、ConversationContext が利用できず、リポジトリの使用時に例外が発生したバッチ処理を行う必要がありました。ConversationContext を使用する予定のない場所でアクティブ化しようとする代わりに、2 つの新しいコンテキスト (+ 1 つのインターセプター) の実装を終了しました。
- 最初のものは ThreadContext (
@ThreadScoped
) で、すべてを a に格納しMap
ましたThreadLocal
(これは非同期処理に適しています) + 1 つのメソッド インターセプター ( @ThreadContextual
) を非同期/バッチ メソッドで使用して、呼び出し時にこのコンテキストをアクティブにします。
- 2 番目のものはもう少し複雑でした。それは、ThreadContext、(NonTransient)ConversationContext、(NonTransient)ViewContext (
@ViewScoped
JSF 2.2 から)、RequestContext の順序で最初のアクティブなコンテキストに委任されたある種の動的コンテキストでした。@UnitOfWorkScoped
対応する注釈を付けて、このコンテキストを UnitOfWorkContext と呼びました。そのコンテキストで生きる必要がある (少数の) Bean に注釈を付けました (私にとっては、それは私の の@Produces
メソッドにすぎませんでしたEntityManager
)。
これらすべてを実装するのは難しいように思えるかもしれませんが、そうではありません。コードはかなり小さいものでした。今のところアクセスできないため、必要に応じて 2 ~ 3 日でコードを貼り付けます。
更新: 2 番目のコンテキストのコードは次のとおりです。
次のインターフェイスは、Context.isActive() を補完するものとして使用されます。コンテキストがアクティブであっても、それを使用したいという意味ではない場合があります。以下の例を参照してください。
public interface DynamicContextActivation {
boolean isActive(Context context);
}
次の注釈を新しいスコープに配置する必要があります
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface DynamicScope {
class DefaultActivation implements DynamicContextActivation {
public boolean isActive(Context context) {
return true;
}
}
Class<? extends Annotation>[] value();
Class<? extends DynamicContextActivation> activation() default DefaultActivation.class;
}
動的コンテキストの実装
public class DynamicContext implements AlterableContext {
private final BeanManager beanManager;
private final DynamicContextActivation activation;
private final Class<? extends Annotation> scope;
private final Class<? extends Annotation>[] scopes;
public DynamicContext(BeanManager beanManager, DynamicContextActivation activation, Class<? extends Annotation> scope, Class<? extends Annotation>[] scopes) {
this.beanManager = beanManager;
this.activation = activation;
this.scope = scope;
this.scopes = scopes;
}
public void destroy(Contextual<?> contextual) {
Context context = getContext();
if (context instanceof AlterableContext) {
((AlterableContext) context).destroy(contextual);
}
}
public <T> T get(Contextual<T> contextual) {
return getContext().get(contextual);
}
public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
return getContext().get(contextual, creationalContext);
}
// Find the first active context
private Context getContext() {
for (Class<? extends Annotation> scope : this.scopes) {
try {
Context context = this.beanManager.getContext(scope);
if (context.isActive() && this.activation.isActive(context)) {
return context;
}
} catch (ContextNotActiveException exception) {
continue;
}
}
return null;
}
public Class<? extends Annotation> getScope() {
return this.scope;
}
public boolean isActive() {
return getContext() != null;
}
}
動的コンテキストを自動登録する拡張機能 (に追加/META-INF/services/javax.enterprise.inject.spi.Extension
)
public class DynamicContextExtension implements Extension {
private final Set<Class<? extends Annotation>> scopes = new HashSet<>();
public void processBean(@Observes ProcessBean<?> bean) {
Class<? extends Annotation> scope = bean.getBean().getScope();
if (scope.isAnnotationPresent(DynamicScope.class)) {
this.scopes.add(scope);
}
}
public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
for (Class<? extends Annotation> scope : scopes) {
DynamicScope dynamicScope = scope.getAnnotation(DynamicScope.class);
try {
// TODO use a managed DynamicContextActivation instead of instantiating it here
DynamicContextActivation activation = dynamicScope.activation().newInstance();
Context context = new DynamicContext(beanManager, activation, scope, dynamicScope.value());
afterBeanDiscovery.addContext(context);
} catch (InstantiationException | IllegalAccessException exception) {
afterBeanDiscovery.addDefinitionError(exception);
}
}
}
}
これは、ThreadScoped、(LongRunning)ConversationScoped、(NonTransient)ViewScoped、RequestScoped の順にスコープを委譲します。
@Retention(RUNTIME)
@NormalScope(passivating = true) // must be true if any of the delegate context is passivation-capable
@DynamicScope(value = {ThreadScoped.class, ConversationScoped.class, ViewScoped.class, RequestScoped.class}, activation = UnitOfWorkActivation.class)
public @interface UnitOfWorkScoped {
class UnitOfWorkActivation implements DynamicContextActivation {
public boolean isActive(Context context) {
if (context.getScope().equals(ConversationScoped.class)) {
// I only want long-running conversations here because in JSF there
// is always a transient conversation per request and it could take
// precedence over all other scopes that come after it
return !CDI.current().select(Conversation.class).get().isTransient();
}
if (context.getScope().equals(ViewScoped.class)) {
// Storing things in view scope when the view is transient gives warnings
return !FacesContext.getCurrentInstance().getViewRoot().isTransient();
}
return true;
}
}
}
sEntityManager
を与えるプロデューサー:@UnitOfWorkScoped
EntityManager
@Stateful // it could work without @Stateful (but Serializable) but I haven't tested enough
@UnitOfWorkScoped
public class EntityManagerProducer {
@PersistenceContext(type = EXTENDED)
private EntityManager entityManager;
@Produces
@UnitOfWorkScoped
@TransactionAttribute(NOT_SUPPORTED)
public EntityManager entityManager() {
return entityManager;
}
}
改善の余地は必ずありますので、遠慮なくフィードバックをお寄せください。
更新 2: DynamicContextActivation を EL 式に置き換えるとよいでしょう
@Retention(RUNTIME)
@NormalScope(passivating = true)
@DynamicScope({
@Scope(scope = ThreadScoped.class),
@Scope(scope = ConversationScoped.class, ifExpression = "#{not javax.enterprise.context.conversation.transient}"),
@Scope(scope = ViewScoped.class, ifExpression = "#{not facesContext.viewRoot.transient}"),
@Scope(scope = RequestScoped.class)
})
public @interface UnitOfWorkScoped {}