在EntityListener中注入SessionScoped有状态bean

时间:2012-07-06 19:38:17

标签: java glassfish ejb javabeans cdi

我正在尝试在GlassFish 3上的Java EE JPA应用程序中实现某种审计。

我在@EntityListeners实体上添加了@MappedSuperclass注释,侦听器在其方法上有@PrePersist@PreUpdate注释,这些注释在运行时很愉快地调用。

在这些方法中,我尝试使用(@Inject@Named@Stateful@SessionScoped bean(UserSession)以获取当前用户的ID。监听器类根本没有注释。

问题是我无法注入UserSession bean;我总是以null值结束。到目前为止,我尝试了 plain @Inject UserSession us;,它总是注入一个空值。我也试过UserSession us = (UserSession) ctx.lookup("java:global/application/module/UserSession");总是返回一个新对象(我验证了构造函数调用,加上对象为空)。

我很确定我错过了一些关于CDI非常重要的东西,但我无法弄清楚是什么。有人可以指出我正确的方向吗?

3 个答案:

答案 0 :(得分:2)

EntityListners不支持CDI,至少在JPA 2.0中是这样。它显然位于JPA 2.1

中的新内容列表中

当我碰到这个时,我也很惊讶。

答案 1 :(得分:2)

  

到这个时候,我尝试了普通的@Inject UserSession us;它总是注入一个空值。

这是因为在JPA 2.0中,类不是由CDI管理的,因此@Inject将无法使用它们。如Steve K所述,这些课程由CDI从JPA 2.1开始管理。

  

我也试过UserSession us =(UserSession)ctx.lookup(“java:global / application / module / UserSession”);

您无法在JNDI中查找由CDI实例化的bean。但是你可以做的是在JNDI中查找CDI BeanManager并从BeanManager中获取bean。 CDI规范保证您始终可以在“java:comp / BeanManager”中找到应用程序中的BeanManager。 这是一个简化的例子:

InitialContext ctx = new InitialContext();
BeanManager bm = ctx.lookup("java:comp/BeanManager");
Set<Bean<?>> beans = bm.getBeans(UserSession.class);
Bean<?> bean = bm.resolve(beans);
CreationalContext<?> ctx = bm.createCreationalContext(bean);
UserSession us = (UserSession) bm.getReference(bean, UserSession.class, ctx);

这将是由CDI管理的Bean,以防会话作用域为UserSession实例。

我的回答是基于CDI injection in EntityListeners

中提出的建议

答案 2 :(得分:1)

我最终找到了一个解决方法,它允许我获取@Stateful bean的引用:

我创建了一个@Named @Singleton @Startup bean SessionController,它包含一个本地HashMap<String, UserSession> sessionMap,其中包含我的@Stateful bean的引用:

@Named
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class SessionController {

private HashMap<String, UserSession> sessionMap;

@PostConstruct
void init() {
    sessionMap = new HashMap<String, UserSession>();
}

@PreDestroy
void terminate() {
    for (UserSession us : sessionMap.values()) {
        us.logoutCleanUp(); //This is annotated as @Remove
    }
    sessionMap.clear();
}

public void addSession(String sessionId, UserSession us) {
    sessionMap.put(sessionId, us);
    System.out.println("New Session added: " + sessionId);
}

public UserSession getCurrentUserSession() {
    FacesContext context = FacesContext.getCurrentInstance();
    String sessionId = ((HttpSession) context.getExternalContext().getSession(false)).getId();
    return sessionMap.get(sessionId);
}

}

我在每个bean的@PostConstruct方法中添加引用:

public class UserSession implements Serializable {
@Inject SessionController sc;
...
    @PostConstruct
    void init() {
    FacesContext context = FacesContext.getCurrentInstance();
    String sessionId = ((HttpSession) context.getExternalContext().getSession(true)).getId();
    sc.addSession(sessionId, this);
}

请注意,由于可能尚未创建会话,因此需要.getSession(true)。另请注意this安全传递,因为@PostConstruct不是构造函数...

毕竟,我可以在我的EntityListener(以及任何其他地方)中获取这样的引用:

SessionController sc = (SessionController) new InitialContext().lookup("java:module/SessionController");
    UserSession us = sc.getCurrentUserSession();

或类似于CDI bean

@Inject SessionController sc;

我看到的唯一缺点是这种方法仅适用于Web应用程序(其中FacesContext context = FacesContext.getCurrentInstance()有意义)。我的一些bean(最后是我的EntityListeners)也通过@javax.jws.WebService作为@Stateless bean公开。在这种情况下(实际上:缺席),我的Singleton无法工作(尚未测试),因为没有任何类型的 sessionId (完全没有会话)。我将不得不使用一种解决方法,可能使用bean的SessionContext或发明某种可用的 sessionId 。如果我创造了可用的东西,我会回复...

相关问题