4

私は@EJB注入された豆を持っていますTransactionCompleteJob。この Bean には@AsynchronousメソッドがありasyncCompleteTransaction(Integer transactionId)ます。

このメソッド内でセッションスコープまたは会話スコープのいずれかである他の注入された Bean およびエンティティを使用しようとすると、エラーが発生します。

WELD-001303: No active contexts for scope type javax.enterprise.context.ConversationScoped

そこで、jboss 溶接ドキュメントBoundConversationScopeで指定されているように、溶接の、BoundSessionScope、およびを注入BoundRequestScopeし、それらをアクティブにして、リクエスト データの空のマップとセッション データの空のマップを生成しました。

問題は、リクエスト スコープを有効にすると、別のエラー メッセージが表示されることです。

WELD-001304: More than one context active for scope type javax.enterprise.context.RequestScoped

リクエスト スコープを有効にしないようにしましたが、実際のリクエスト スコープにあるもののリソース リークが発生するようです。具体的には、リクエスト スコープの JPA がありEntityManagerます。特に、プロセスが終了すると、別のメッセージが表示されます。

WELD-000019: Error destroying an instance org.hibernate.jpa.internal.EntityManagerImpl@5df070be of Producer Method [EntityManager] with qualifiers [@RequestScopey @Any] declared as [[BackedAnnotatedMethod] @Produces @RequestScoped @RequestScopey public packagename.entitymanager.EntityManagerProducer.createRequestScopedEntityManager()]

すでにアクティブなリクエスト スコープ コンテキストがある場合、どのようにリクエスト スコープ コンテキストを開始できますか? または、既存のリクエスト スコープ コンテキストに結び付くセッション スコープ コンテキストと会話スコープ コンテキストを開始しますか? あるいは、この問題を回避するためのより良い方法はありますか?

編集:

RequestScope独自の溶接を開始する前に、それを非アクティブ化できるように、溶接から取得する方法はありますか? または、TransactionCompleteJobそれを注入してメソッドを呼び出すことなく、非同期的に非同期的に開始する@Asynchronous方法はありますか?

4

1 に答える 1

4

多かれ少なかれ同じ問題がありましたが、別のアプローチを取りました@ConversationScoped EntityManager。リポジトリに注入しましたが、ConversationContext が利用できず、リポジトリの使用時に例外が発生したバッチ処理を行う必要がありました。ConversationContext を使用する予定のない場所でアクティブ化しようとする代わりに、2 つの新しいコンテキスト (+ 1 つのインターセプター) の実装を終了しました。

  • 最初のものは ThreadContext ( @ThreadScoped) で、すべてを a に格納しMapましたThreadLocal(これは非同期処理に適しています) + 1 つのメソッド インターセプター ( @ThreadContextual) を非同期/バッチ メソッドで使用して、呼び出し時にこのコンテキストをアクティブにします。
  • 2 番目のものはもう少し複雑でした。それは、ThreadContext、(NonTransient)ConversationContext、(NonTransient)ViewContext ( @ViewScopedJSF 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 {}
于 2015-05-26T12:02:56.250 に答える