Search code examples
cdiweld

CDI: Which instance's @Observes-method is called ?


I observe a strange behavoir for cdi events:

Given the following scenario: An action on the admin page shall update the view of web page news.xhtml (via omnifaces) by adding the value for "Event-Id":

enter image description here

Both beans are session scoped backing beans for their jsf based web pages.

What happens: There are two instances of NewsBean due to two browser windows accessing news.xhtml. When an event is fired, both browser windows are updated. However, both updates come from the @Observes-method of the first instance of NewsBean.

More precisely: NewsBean has a property "id" that is initialized @PostConstruct with a random number. This id is displayed on news.xhtml as "Property-Id" (cf. image). The id is also pushed by the @Observes-method of the bean and displayed on news.xhtml as "Event-Id":

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

Let the id's for the two instances simply be 1 and 2. Before the update, browser windows for news.xhtml display:

Browser 1:                  Browser 2: 

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

After the update (Event-Id value added):

Browser 1:                  Browser 2: 

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

That means:

  • both instances of NewsBean observed the event and updated their associated browser window
  • but the data send to the two browsers comes from the @Observes-method of the first instance

I had expected that each instance's own @Observes-method would be called.

Q1: Is this behavoir intended ?

Q2: Is the @Observes-method allowed to use internal state of the bean instance ? (in this case it would be a bug)

As cdi container I used Weld from WildFly 11. Code for NewsBean is:

@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 ... 
}

Code of 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> 

Solution

  • I'll try and give some answer to address your concerns:

    Q1: Is this behavior intended ?

    Yes, it is. CDI only picks once observer method (OM) and uses that when it comes to notification. From the top of my head, having multiple OMs, you would introduce some serious troubles with ordering, dependent beans, exception chains and context thread propagation (which spec forbids).

    Q2: Is the @Observes-method allowed to use internal state of the bean instance ? (in this case it would be a bug)

    Definitely, and it is not a bug. Imagine it's @ApplicationScoped bean. It makes perfect sense to rely on bean's internal state and/or change it. Not to mention it would be near impossible to tell whether observer method's code does access internal state or not.

    What happens is that when you fire an event, an observer will be notified within your current context. For instance, when having two sessions, you will have at least two contexs. OM will be used for the one within whose context the event was fired.