Java 是否可以从现有请求范围激活会话范围和对话范围?

Java 是否可以从现有请求范围激活会话范围和对话范围?,java,jakarta-ee,ejb,weld,conversation-scope,Java,Jakarta Ee,Ejb,Weld,Conversation Scope,我有一个@EJB注入beanTransactionCompleteJob。这个bean上有一个@Asynchronous方法asynchcompletetransaction(Integer transactionId) 当我尝试使用此方法中会话范围或会话范围的其他注入bean和实体时,我最终得到一个错误: WELD-001303:作用域类型javax.enterprise.context.ConversationScoped没有活动上下文 因此,我注入了weld的BoundConversati

我有一个
@EJB
注入bean
TransactionCompleteJob
。这个bean上有一个
@Asynchronous
方法
asynchcompletetransaction(Integer transactionId)

当我尝试使用此方法中会话范围或会话范围的其他注入bean和实体时,我最终得到一个错误:

WELD-001303:作用域类型javax.enterprise.context.ConversationScoped没有活动上下文

因此,我注入了weld的
BoundConversationScope
BoundSessionScope
BoundRequestScope
并激活了它们,为请求数据生成了一个空映射,为会话数据生成了一个空映射,具体如下:

问题是,在激活请求范围时,我收到另一条错误消息:

WELD-001304:作用域类型javax.enterprise.context.RequestScoped的多个上下文处于活动状态

我试图不激活请求范围,但我似乎最终导致了实际请求范围内的任何内容的资源泄漏,特别是我有一个请求范围为JPA
EntityManager
。尤其是在流程完成后,我看到另一条消息:

WELD-000019:销毁实例org.hibernate.jpa.internal时出错。EntityManagerImpl@5df070be使用声明为[[BackedAnnotatedMethod]@的限定符[@RequestScopey@Any]生成@RequestScoped@RequestScopey public packagename.EntityManager.EntityManager.EntityManager.Producer.createRequestScopedEntityManager()的生成程序方法[EntityManager]

当一个请求范围上下文已处于活动状态时,如何启动该上下文?或者启动与现有请求范围上下文关联的会话范围上下文和会话范围上下文?或者,有没有更好的方法来回避这个问题

编辑:


有没有办法从weld获得
RequestScope
,这样我就可以在开始自己的工作之前停用它?或者异步启动我的
TransactionCompleteJob
的方法,没有注入它并调用
@Asynchronous
方法?

我或多或少遇到了相同的问题,但采取了不同的方法:我在我的存储库中注入了一个
@conversationscope
,但随后我需要在没有可用的会话上下文且出现异常的情况下进行批处理使用我的存储库时。我没有尝试在不打算使用的地方激活ConversationContext,而是实现了2个新的上下文(+1个拦截器):

  • 第一个是ThreadContext(
    @ThreadScoped
    ),它将所有内容存储在
    映射中的
    ThreadLocal
    (这对于异步处理很好)+1个方法拦截器(
    @ThreadContext
    )中,用于我的异步/批处理方法,以便在调用时激活此上下文
  • 第二个有点复杂:它是一种动态上下文,按顺序委托给第一个活动上下文:ThreadContext,(非瞬态)ConversationContext,(非瞬态)ViewContext(
    @ViewScoped
    ,来自JSF 2.2),RequestContext。我使用相应的
    @UnitOfWorkScoped
    注释调用了这个上下文UnitOfWorkContext。我注释了需要在该上下文中生存的(少数)bean(对我来说,这只是我的
    EntityManager的
    @products
    方法)
实现所有这些似乎很困难,但事实并非如此,代码非常小。如果需要,我将在2-3天内粘贴我的代码,因为我暂时无法访问它

更新:以下是第二个上下文的代码:

以下接口用作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;
}
此作用域用于委托ThreadScope、(LongRunning)ConversationScope、(非瞬态)ViewScope、RequestScope:

@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;
        }
    }
}
一个
EntityManager
生产者,提供
@unitofworkscope
EntityManager
s:

@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:最好用EL表达式替换DynamicContextActivation

@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 {}

不,那不是重复的。我知道如何激活作用域,如我在问题中所述。我的问题是当我已经有一个请求范围时激活对话和会话范围……第二个更复杂的上下文的代码将非常有用。实际上,我已经重新编译了我所有的代码,并触发了一个没有上下文的新线程抖动(然后我激活了相关的上下文,到目前为止没有任何问题)。但是我不喜欢这个代码——在Java EE环境中启动新线程似乎有点异味。我知道它在技术上没有回答我的问题,但DynamicScope的想法非常好,我相信我可以适应它来消除代码中的代码异味。所以我给了你赏金…谢谢。如果您感兴趣,我将使用EL表达式而不是DynamicContextActivation重写代码。这样代码就更简洁了。
@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;
    }
}
@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 {}