会话超时触发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
相关产品推荐
相关产品推荐

