【问题标题】:Injecting a SessionScoped Stateful bean in EntityListener在 EntityListener 中注入 SessionScoped Stateful bean
【发布时间】:2012-07-07 07:03:04
【问题描述】:

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

我在@MappedSuperclass 实体上添加了@EntityListeners 注释,侦听器在其方法上有@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 的一些非常重要的内容,但我不知道是什么。有人可以指点我正确的方向吗?

【问题讨论】:

    标签: java glassfish ejb javabeans cdi


    【解决方案1】:

    EntityListner 不支持 CDI,至少在 JPA 2.0 中是这样。它显然在 JPA 2.1 的新事物列表中

    当我遇到这个时我也很惊讶。

    【讨论】:

    • 那么有没有办法获取这个实例呢?甚至没有 JNDI 查找?
    • 我们通过做一些疯狂的 ThreadLocal 东西来解决这个问题,希望有人有更好的建议!
    【解决方案2】:

    我最终找到了一种解决方法,它可以让我获得@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。如果我创造了一些有用的东西,我会回帖......

    【讨论】:

      【解决方案3】:

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

      这是因为在 JPA 2.0 中,类不是由 CDI 管理的,所以 @Inject 不能与它们一起使用。正如 Steve K 所指出的,这些类从 JPA 2.1 开始由 CDI 管理。

      我也试过 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中提出的内容

      【讨论】:

      • 感谢您的回答。这绝对看起来很可靠。这个方法如何保证返回正确的(意思是:当前用户/调用的正确会话)SFSB?
      • 如果你定义一个 sessionscoped bean 并以这种方式查找它,cdi 规范保证你总是在执行线程上获得属于 session 的实例。因此,如果您使用的 CDI 实现是规范的。符合,然后它会工作。这很可能也适用于 SFSB。
      • 非常有帮助!但在最后一行而不是 bean.getClass() 中应该是 UserSession.class。否则我会收到错误 WELD-001305。
      • 对,应该是UserSession.class或者bean.getBeanClass()。 getClass() 显然是错误的,因为它返回 Bean >。我不知道我在 2013 年写这篇文章时在想什么。 :-) 感谢您的提醒,我修正了我的答案。
      猜你喜欢
      • 2014-10-20
      • 2011-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-04
      • 2019-04-22
      相关资源
      最近更新 更多