【问题标题】:CDI: Which instance's @Observes-method is called ?CDI:调用了哪个实例的@Observes-method?
【发布时间】:2018-02-14 20:57:14
【问题描述】:

我观察到 cdi 事件的一种奇怪行为:

假设以下场景:管理页面上的操作应通过添加“Event-Id”的值来更新网页 news.xhtml 的视图(通过omnifaces):

这两个 bean 都是基于 jsf 的网页的会话范围的支持 bean。

会发生什么: 由于有两个浏览器窗口访问 news.xhtml,因此有两个 NewsBean 实例。当一个事件被触发时,两者 浏览器窗口已更新。但是,这两个更新都来自 NewsBean 第一个实例的@Observes-method

更准确地说:NewsBean 有一个属性“id”,它使用随机初始化 @PostConstruct 数字。此 id 在 news.xhtml 上显示为“Property-Id”(参见图片)。 id也被推送 通过 bean 的 @Observes-method 并在 news.xhtml 上显示为“Event-Id”:

private void onBreakingNews(@Observes Info info) {       
  channel.send(id);  // displayed as "Event-Id" on news.xhtml      
} 

让两个实例的 id 简单地为 1 和 2。在更新之前,news.xhtml 的浏览器窗口显示:

Browser 1:                  Browser 2: 

Property-Id: 1              Property-Id: 2
Event-Id:                   Event-Id:  

更新后(添加Event-Id值):

Browser 1:                  Browser 2: 

Property-Id: 1              Property-Id: 2
Event-Id: 1                 Event-Id: 1 

意思是:

  • NewsBean 的两个实例都观察到该事件并更新了它们关联的浏览器窗口
  • 但是发送到两个浏览器的数据来自第一个实例的@Observes-method

我曾期望每个实例自己的@Observes-method 都会被调用。

Q1:这种行为是有意的吗?

Q2: 是否允许@Observes-method 使用bean 实例的内部状态? (在这种情况下,这将是一个错误)

作为 cdi 容器,我使用 WildFly 11 中的 Weld。NewsBean 的代码是:

@Named @SessionScoped
public class NewsBean {

  private int id; 
  @Inject @Push(channel="pushChannel") private PushContext channel;  

  private void onBreakingNews(@Observes Info info) { 
    // channel.send(info.getMsg()); 
    channel.send(id); 
  } 

  @PostConstruct 
  private void init() { 
    id = new Random().nextInt(100); 
  } 

  ... getter and setter for id ... 
}

news.xhtml代码:

<h:head>
  <f:verbatim>
    <script type="text/javascript">
      function socketListener(message, channel, event) {
        document.getElementById("formId:info").value = message;
      }; 
    </script>
  </f:verbatim>
</h:head>
<h:body>
  <h:outputText value="Property-Id: "/>
  <h:outputText value="#{newsBean.id}"/>
  <p></p>
  <h:form id="formId">
    <h:outputText value="Event-Id: "/>       
    <h:inputText value="" id="info" readonly="true"/>
    <o:socket channel="pushChannel" onmessage="socketListener"/>       
  </h:form>
</h:body> 

【问题讨论】:

  • 尝试添加一些登录到观察者方法的方法。我想说只通知一个观察者方法(我希望从 CDI 获得)但您的 channels 正在扩大对所有这些视图的更改
  • 是的,我在观察者方法中有一个 println,并且服务器日志中只有一个条目(事实上,这就是我注意到这个主题的方式)。这意味着,观察者方法只能使用所有 bean 实例共享的状态。这是一个非常重要的限制。令我恼火的是,CDI 文献中从未提及这一点(例如,Finnigan 关于 Weld 的书对此一无所知)。
  • 嗯,CDI 是一个相当庞大的规范,并且集成在许多地方。你不能希望把它全部写下来(还有灰色区域)。除此之外,它很可能写在其他地方……在另一个规范中等等。

标签: cdi weld


【解决方案1】:

我会尝试给出一些答案来解决您的问题:

Q1:这种行为是有意的吗?

是的,是的。 CDI 只选择一次观察者方法 (OM) 并在通知时使用它。在我看来,拥有多个 OM 会在排序、依赖 bean、异常链和上下文线程传播(规范禁止)方面引入一些严重的问题。

Q2:@Observes 方法是否允许使用 bean 实例的内部状态? (在这种情况下,这将是一个错误)

当然,这不是错误。想象一下它是@ApplicationScoped bean。依赖 bean 的内部状态和/或更改它是非常有意义的。更不用说判断观察者方法的代码是否访问内部状态几乎是不可能的。

发生的情况是,当您触发事件时,将在您的current context 中通知观察者。例如,当有两个会话时,您将至少有两个上下文。 OM 将用于触发事件的上下文。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 2016-05-24
    • 1970-01-01
    相关资源
    最近更新 更多