So far, we worked exclusively on the presentation layer. We now introduce a further layer of so-called services to implement the actual business logic. The use of services is a procedural approach whereby individual service calls modify the business objects (entities), while these business objects do not contain any business logic themselves.
The individual service methods are grouped in service classes according to their theme. We will see in the following section, for example, how a CampaignService
is introduced to process Campaign
objects.
Other aspects can be added to the service calls in later iterations. They can be used to persist entities, establish transaction control and safeguard the security of the application.
Using CDI, we will focus exclusively on the structure of the service classes. As in previous chapters, we won’t pursue the approach of adding new functionalities to the application, but of modifying our target architecture. As we do so, we will learn a couple more important features of CDI.
For those wishing to learn more about the service pattern, we recommend the lecture Real World Java EE Patterns – Rethinking Best Practices.
5.1 Adding the Mock Method to a Service Class
Currently, we are using the method createMockCampaigns
to create a list of sample campaigns in the bean CampaignListProducer
. The method is invoked by the method init
. We want to modify this such that the list of campaigns will be returned by a service call.
To do this, we will first create the interface CampaignService
– from the package de.dpunkt.myaktion.services – which we will implement in the upcoming service class. The source code of the interface is shown in Listing 5-1.
package press.turngeek.mycampaign.services; import press.turngeek.mycampaign.model.Campaign; import java.util.List; public interface CampaignService { List<Campaign> getAllCampaigns(); }
Listing 5-1 CampaignService interface
The interface of the service currently contains only one method, getAllCampaigns
, whose purpose is to return the list of Campaign
objects. It would technically be possible to implement the service directly, without an interface; however, since we want to use a mock implementation in addition to an implementation with real data, an interface is necessary.
To call the declared service, we must modify the init
method of the CampaignListProducer
bean as follows:
@Inject private CampaignService campaignService; @PostConstruct public void init() { campaigns = campaignService.getAllCampaigns(); }
The annotation @Inject
to add another bean of the type CampaignService
as well as using the method init
to invoke its method getAllCampaigns
, the purpose being to obtain the list of Campaign
objects. Now, it should hopefully be clear why we converted the constructor into a method with the annotation @PostConstruct
in section 3.3.1; if we hadn’t done this, the CampaignService
bean would not be available to us now.
If we start the application, we’ll see that there is still a small problem: since we have so far only defined the interface of the service, and have not yet implemented a service class, the container cannot find an implementation. When deployed, it therefore shows the error message: WELD-001408 Unsatisfied dependencies for type [CampaignService]
.
In light of this, we now require an implementation of the CampaignService
. This implementation will contain only the method getAllCampaigns
, which we will take from the createMockCampaigns
method of the CampaignListProducer
bean. The complete implementation is shown in Listing 5-2; the class MockCampaignServiceBean
must be stored in the package press.turngeek.mycampaign.services
. After restarting the application, the dependency will be correctly resolved, which means that CDI, using the MockCampaignServiceBean
, will find the correct implementation of the bean CampaignService
.
package press.turngeek.mycampaign.services; import press.turngeek.mycampaign.model.Account; import press.turngeek.mycampaign.model.Campaign; import press.turngeek.mycampaign.model.Donation; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Alternative; import java.util.LinkedList; import java.util.List; @RequestScoped public class MockCampaignServiceBean implements CampaignService { public List<Campaign> getAllCampaigns() { Donation donation1 = new Donation(); donation1.setDonorName("John Smith"); donation1.setAmount(20d); donation1.setReceiptRequested(true); donation1.setStatus(Donation.Status.TRANSFERRED); donation1.setAccount(new Account(donation1.getDonorName(), "XXX Bank","DE44876543210000123456")); Donation donation2 = new Donation(); donation2.setDonorName("Peter Jones"); donation2.setAmount(30d); donation2.setReceiptRequested(false); donation2.setStatus(Donation.Status.IN_PROCESS); donation2.setAccount(new Account(donation2.getDonorName(), "YYY Bank","DE44864275310000654321")); List<Donation> spenden = new LinkedList<>(); spenden.add(donation1); spenden.add(donation2); Campaign campaign1 = new Campaign(); campaign1.setName("Shirts for the U19 Team"); campaign1.setTargetAmount(1000d); campaign1.setAmountDonatedSoFar(258d); campaign1.setDonationMinimum(20d); campaign1.setId(1L); campaign1.setAccount(new Account("Robert Cook", "ABC Bank", "DE44123456780100200300")); campaign1.setDonations(spenden); Campaign campaign2 = new Campaign(); campaign2.setName("Wheelchair for Maria"); campaign2.setTargetAmount(2500d); campaign2.setAmountDonatedSoFar(742d); campaign2.setDonationMinimum(25d); campaign2.setId(2L); campaign2.setAccount(campaign1.getAccount()); campaign2.setDonations(spenden); List<Campaign> ret = new LinkedList<>(); ret.add(campaign1); ret.add(campaign2); return ret; } }
Listing 5-2 MockCampaignServiceBean class
5.2 Select the Service Implementation to Use with Qualifiers
Our sample campaigns from the mock implementation MockCampaignServiceBean
are all well and good, but they do not fulfil the requirements of the application. For this reason, we will now write the CampaignServiceBean
– another implementation of the interface CampaignService
. Like the previous implementation, it will be stored in the package press.turngeek.mycampaign.services
and can be found in Listing 5-3.
package press.turngeek.mycampaign.services; import press.turngeek.mycampaign.model.Campaign; import javax.enterprise.context.RequestScoped; import java.util.LinkedList; import java.util.List; @RequestScoped public class CampaignServiceBean implements CampaignService { @Override public List<Campaign> getAllCampaigns() { return new LinkedList<Campaign>(); } }
Listing 5-3 CampaignServiceBean class
This implementation returns only an empty list of Campaign
objects. In later iterations we might add a delegation to a persistence layer here to retrieve the actual list of campaigns from the database. But for the time being, for explaining CDI, an empty list is totally sufficient.
If we start the application again, a fresh error message will appear: WELD-001409 Ambiguous dependencies for type [CampaignService]
.
In contrast to the previous section, we now have the problem that CDI finds multiple implementations of the CampaignService
bean and cannot decide for itself which one is to be used.
As we did with the ambiguities in our messages (see section 4.2), we can resolve this problem using a qualifier. This solution is not required for our application, since we will tackle the issue using an alternative approach. However, if we wish to see how the qualifier solution works, we can test it out by creating the class TestQualifier
with the qualifier @MyService
from Listing 5-4 and saving it in the package press.turngeek.mycampaign.util
.
package press.turngeek.mycampaign.util; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; public class TestQualifier { @Qualifier @Target({ FIELD, TYPE }) @Retention(RUNTIME) public @interface MyService { } }
Listing 5-4 TestQualifier class
On one end, the qualifier @MyService
must be added to the attribute campaignService
within the bean CampaignListProducer
:
@Inject @MyService private CampaignService campaignService;
…and, on the other end, to the implementation of CampaignService
that is to be used:
@RequestScoped @MyService public class CampaignServiceBean implements CampaignService {
If we start the application once more, we will see that the ambiguities have now been resolved and that the application uses the implementation CampaignServiceBean
for the CampaignService
. The class diagram in Fig. 5-1 illustrates the classes involved.
Fig. 5-1 Relationships of the classes involved in the unambiguous injection of a bean
We have now shown how ambiguities at the injection point can be resolved with the help of a qualifier. However, as mentioned above, we will not be using this approach for our application, and so the annotations and the class TestQualifier
added in the last step should now be removed.
Instead of resolving the ambiguities with qualifiers, we can choose to use the annotation @Alternative
from the package javax.enterprise.inject
.
To follow this approach, we need simply to add this annotation to the bean MockCampaignServiceBean
:
@RequestScoped @Alternative public class MockCampaignServiceBean implements CampaignService {
This annotation designates the bean as an alternative, non-default implementation. Instead of using this, the container will use the remaining implementation, CampaignServiceBean
. Therefore we once again have a definitive resolution.
If we want to invoke an alternative implementation, we need to specify this explicitly in the CDI configuration file. We haven’t needed to do this so far, since we have relied solely on the standard configuration of CDI.
We will change this now for the sake of testing the invocation of an alternative implementation. Specifically, we will save the sample configuration from Listing 5-5 in the file WEB-INFbeans.xml
. After the application has been restarted, the mock implementation will be used. This shows how simple it is to use different service implementations, regardless of the deployment scenario.
Since the implementation CampaignServiceBean
is required for the upcoming iterations, we now need to delete the file WEB-INFbeans.xml
, causing CDI to revert back to the standard configuration.
In CDI 1.0 (part of Java EE 6), the mere existence of the configuration file beans.xml
– even when empty – was the switch to enable CDI for all classes of the application. Up until now, however, beans.xml
has not existed in our project – and CDI has functioned anyway!
Since CDI Version 1.1 (part of Java EE 7), it has been possible, using beans.xml
, to influence which classes of the application are recognized as CDI beans (attribute bean-discovery-mode
). If an application does not possess a beans.xml
file, CDI behaves as though the attribute bean-discovery-mode
has been assigned the value annotated
. In this case, the only classes recognized as CDI beans are those that have been explicitly assigned a scope annotation. If we assign the attribute the value all
, we achieve the same behavior as in CDI 1.0. Further details on possible settings can be found in the CDI specification.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="annotated"> <alternatives> <class>press.turngeek.mycampaign.services.MockCampaignServiceBean</class> </alternatives> </beans>
Listing 5-5 XML file beans.xml
In this section, we have explored two methods for resolving ambiguities: qualifiers and alternatives. The two are used in different cases:
Qualifiers are used when multiple implementations of the same type are used during the runtime of the application. During the development stage, the programmer uses qualifiers to determine definitively which implementation should be used.
Alternatives are different in that implementations are defined per deployment. This has the result that only one implementation exists at runtime.
We have now completed the development part of our CDI tutorial! We recommend you compare your results with the snapshot of the project in our Cloud IDE:
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.