You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

会话超时触发sessionDestroyed时调用@Repository报ContextNotActiveException

解决会话超时销毁时的ContextNotActiveException问题

根本原因:会话超时由容器后台线程触发,此时无活跃HTTP请求,RequestScoped上下文不存在,而DeltaSpike Data的Repository默认依赖RequestScoped的EntityManager,调用Repository方法时会抛出ContextNotActiveException。

以下是几种可行解决方案:

方案1:手动启动并管理RequestScoped上下文

通过DeltaSpike的ContextControl手动启动RequestScoped上下文,确保Repository调用在上下文活跃期执行,执行完成后必须关闭上下文避免内存泄漏。

修改后的监听器代码:

import applicationname.model.entities.login.UserSession;
import applicationname.model.repositories.login.UserSessionRepo;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.api.context.ContextControl;

@WebListener
public class LogoutBean implements HttpSessionListener {
    private UserSessionRepo repository;
    private ContextControl contextControl;

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // 延迟获取Bean,避免监听器初始化早于CDI容器导致的注入失败
        if (repository == null) {
            repository = BeanProvider.getContextualReference(UserSessionRepo.class, false);
            contextControl = BeanProvider.getContextualReference(ContextControl.class, false);
        }

        // 启动RequestScoped上下文
        contextControl.startContext(jakarta.enterprise.context.RequestScoped.class);
        try {
            UserSession sesion = repository.findBySessionId(se.getSession().getId());
            doThingsToUpdateTheFieldsOfTheEntityLikeSettingLogoutTimeAndStuff(sesion);
            repository.save(sesion);
        } finally {
            // 关闭上下文
            contextControl.stopContext(jakarta.enterprise.context.RequestScoped.class);
        }
    }
}

方案2:使用ApplicationScoped的EntityManager直接操作

绕过DeltaSpike Repository,创建ApplicationScoped服务直接注入EntityManager,脱离对RequestScoped上下文的依赖。

1. 定义ApplicationScoped的EntityManager生产者

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@ApplicationScoped
public class EntityManagerProducer {
    @PersistenceContext
    private EntityManager entityManager;

    @Produces
    @ApplicationScoped
    public EntityManager produceEntityManager() {
        return entityManager;
    }
}

2. 创建ApplicationScoped的业务服务

import applicationname.model.entities.login.UserSession;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;

@ApplicationScoped
public class UserSessionService {
    @Inject
    private EntityManager entityManager;

    @Transactional
    public void updateLogoutInfo(String sessionId) {
        UserSession session = entityManager.createQuery(
                "SELECT u FROM UserSession u WHERE u.sessionId = :sessionId", UserSession.class)
                .setParameter("sessionId", sessionId)
                .getSingleResult();
        // 设置登出时间等字段
        session.setLogoutTime(LocalDateTime.now());
        entityManager.merge(session);
    }
}

3. 修改监听器代码

import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.apache.deltaspike.core.api.provider.BeanProvider;

@WebListener
public class LogoutBean implements HttpSessionListener {
    private UserSessionService sessionService;

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        if (sessionService == null) {
            sessionService = BeanProvider.getContextualReference(UserSessionService.class, false);
        }
        sessionService.updateLogoutInfo(se.getSession().getId());
    }
}

方案3:调整DeltaSpike Repository的EntityManager作用域

通过配置让DeltaSpike Repository使用ApplicationScoped的EntityManager,需配合自定义限定符实现。

1. 自定义限定符注解

import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface ApplicationScopedEntityManager {}

2. 修改EntityManager生产者

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@ApplicationScoped
public class EntityManagerProducer {
    @PersistenceContext
    private EntityManager entityManager;

    @Produces
    @ApplicationScoped
    @ApplicationScopedEntityManager
    public EntityManager produceApplicationScopedEntityManager() {
        return entityManager;
    }
}

3. 修改Repository接口

import applicationname.model.entities.login.UserSession;
import org.apache.deltaspike.data.api.EntityRepository;
import org.apache.deltaspike.data.api.Repository;
import org.apache.deltaspike.data.api.TransactionalConfig;

@Repository(forEntity = UserSession.class)
@TransactionalConfig(entityManagerQualifier = ApplicationScopedEntityManager.class)
public interface UserSessionRepo extends EntityRepository<UserSession, Integer> {
    UserSession findBySessionId(String sessionId);
}

内容的提问来源于stack exchange,提问作者El Suscriptor Justiciero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 02:54:51