Up until now, we have learned that beans managed by a container can be linked with each other using CDI. However, beans are not the only thing for which this applies: the principle of loose coupling of components with CDI goes one step further, as during runtime, components can send and receive any message the user wishes. The components no longer have dependencies to each other, but rather solely to the class that contains the message. This kind of loose coupling enables additional receiving components to be added without the sending component having to be modified. This improves the extensibility and maintainability of the application.
4.1 Sending and Receiving Events
As detailed in section 2.3, a troublesome dependency exists from the EditCampaignController
to the CampaignListProducer
bean. By having the EditCampaignController
send a message whenever a campaign is added, CDI offers a simple way to remove this dependency. The CampaignListProducer
can then respond to this message and, since it manages the list of all Campaign
objects, can add the new Campaign
object to this list.
We will begin by changing the EditCampaignController
such that when a campaign is added, a corresponding message is sent. The crucial element here is the message we will send: since we want a Campaign
object to be added, it is sufficient – for the time being – to simply send this as the message. The message to be sent is therefore of the type Campaign
. Since CDI sends messages via the generic class Event
from the package javax.enterprise.event
, Campaign
must be set as the type parameter for this class. To achieve this, we must inject an attribute of the type Event<Campaign>
into the bean by adding the following lines of code:
@Inject private Event<Campaign> campaignAddEvent;
The corresponding message must now be sent in the method doSave
instead of the bean CampaignListProducer
being directly accessed. To do this, the following line:
campaignListProducer.getCampaigns().add(campaignProducer.getSelectedCampaign());
must be replaced by
campaignAddEvent.fire(campaignProducer.getSelectedCampaign());
Using the fire
method of the Event
object, the actual message of the type Campaign
will now be sent.
Since the dependency to the CampaignListProducer
is no longer required – which is a good thing for our application – the following lines can now be deleted:
@Inject private CampaignListProducer campaignListProducer;
Now, the bean EditCampaignController
will send the new Campaign
object as a message whenever a new campaign is added. Other beans can now receive this message as desired. To enable this to happen, the receiving bean must implement a method that has a Campaign
object as its only parameter. The parameter must have the annotation @Observes
from the package javax.enterprise.event
.
Currently, the message is only of interest for the CampaignListProducer
. In accordance with the above, this bean must therefore implement the following method:
public void onCampaignAdded(@Observes Campaign campaign) { getCampaigns().add(campaign); }
This method is now invoked synchronously by the container as soon as the EditCampaignController
sends the message regarding the adding of a Campaign
object. The container passes the Campaign
object to be added as a parameter. In the example, this is added to the list of Campaign
objects using the method add
.
4.2 Differentiate Between Messages of the Same Type Using Qualifiers
Following the same principle as with the message for a new campaign, we can now send a message about the deletion of a campaign. To do this, we must modify the bean ListCampaignsController
with an Event
object that sends a message of the type Campaign
:
@Inject private Event<Campaign> campaignDeleteEvent;
To ensure that the message is sent, the fire method of the Event
object must be passed as a parameter with the campaign to be deleted. To achieve this, the method commitDeleteCampaign
must be replaced with the following:
public void commitDeleteCampaign() { campaignDeleteEvent.fire(campaignToDelete); }
If we start the program now, we are faced with the problem that CDI cannot differentiate between the messages for the adding and deletion of a campaign. CDI differentiates between messages on the basis of their type; in this example, the type is the same for both events. The result is that the CampaignListProducer
adds a Campaign
object in both cases: when a campaign is actually being added (as we want it to), but also when a campaign is being deleted. This means that when a user deletes an object, it ends up being added to the list once more instead. Please have a try yourself.
To differentiate between the two messages, we require a further central concept of CDI: a so-called qualifier.
A qualifier is a user-defined annotation that serves to resolve ambiguities. In this specific case, we need two qualifiers to differentiate between our two different messages. These are specified using the names Added
and Deleted
in the class Events
belonging to the package press.turngeek.mycampaign.util
(see Listing 4-1).
package press.turngeek.mycampaign.util; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; public class Events { @Qualifier @Target({ FIELD, PARAMETER }) @Retention(RUNTIME) public @interface Added { } @Qualifier @Target({ FIELD, PARAMETER }) @Retention(RUNTIME) public @interface Deleted { } }
Listing 4-1 Events class
Both annotations have been given the annotation @Qualifier
from the package javax.inject
; likewise, both are valid for attributes and parameters, a fact that is indicated by the annotation @Target
.
To enable CDI to differentiate between the two messages, the Event
object campaignAddEvent
in EditCampaignController
must now be given the annotation @Added
:
@Inject @Added private Event<Campaign> campaignAddEvent;
Similarly, the Event
object campaignDeleteEvent
in ListCampaignsController
must be given the annotation @Deleted
:
@Inject @Deleted private Event<Campaign> campaignDeleteEvent;
As a result of this change, CDI can now differentiate between the messages in spite of them being of the same type.
To enable the application to react based on the message, the CampaignListProducer
must be modified. This is done in two steps: firstly, the existing method with the annotation @Observes
is modified with the qualifier @Added
:
public void onCampaignAdded(@Observes @Added Campaign campaign) { getCampaigns().add(campaign); }
…and then a similar method is added in the same bean to respond to the message with the qualifier @Deleted
:
public void onCampaignDeleted(@Observes @Deleted Campaign campaign) { getCampaigns().remove(campaign); }
This method simply removes the passed Campaign
object from the list of Campaign
objects by invoking the method remove
. Incidentally, this represents an extension of the application’s functionality; now, when the user presses the button to confirm the deletion of a campaign, it will be removed from the list of campaigns.
Fig. 4-1 shows the relationships between the beans and the qualifier.
Fig. 4-1 Relationships between the classes involved in the sending and receiving of messages
Before continuing, you may know compare your workspace with the provided link:
Discussion
Use the message board below to give the authors your feedback or to discuss this page’s topic with other readers (in English please!). Please don’t expect the authors to answer directly, but they might update the content of this site according to your feedback.