Java EE CDI Producer methods tutorial
Introduction
Java EE CDI introduced a concept called Producer. Producers may be used to create - or produce - bean instances to be consumed by an application. Producers are also able to provide specific interface implementations according to the consumer needs so they are a valid way to support polymorphism in a CDI application.
Producers are also useful to encapsulate bean initialization or even to enable injection of object instances that are not themselves CDI managed beans into some points of our application.
This tutorial considers the following environment:
- Ubuntu 12.04
- JDK 1.7.0.21
- Weld 1.1.10
- Tomcat 7.0.35
Weld is the CDI reference implementation
The service
In this tutorial we will implement a Producer that will create and provide an example message sending service to its consumers. At the very beginning we will assume a single service implementation that represents message sending through email. Later we will define a second service implementation that will represent message sending through SMS.
Let's start by defining the service interface and a simple implementation:
package com.byteslounge.bean; public interface MessageSender { void sendMessage(); }
package com.byteslounge.bean.impl; import com.byteslounge.bean.MessageSender; public class EmailMessageSender implements MessageSender { @Override public void sendMessage() { System.out.println("Sending email message"); } }
So we defined the service interface and one implementation. In this scenario we defined the implementation that sends the message as an email message.
The producer
Now we define a CDI managed bean containing a Producer method that will be used to create MessageSender instances:
package com.byteslounge.bean; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Produces; import com.byteslounge.bean.impl.EmailMessageSender; @SessionScoped public class MessageSenderFactory implements Serializable { private static final long serialVersionUID = 5269302440619391616L; @Produces public MessageSender getMessageSender(){ return new EmailMessageSender(); } }
The method getMessageSender() is annotated with @Produces so the CDI container will know it produces MessageSender implementations. Now we may inject MessageSender implementations into our application:
@Inject private MessageSender messageSender;
Multiple bean implementations (Polymorphism)
Let's define a second MessageSender implementation:
package com.byteslounge.bean.impl; import com.byteslounge.bean.MessageSender; public class SmsMessageSender implements MessageSender { @Override public void sendMessage() { System.out.println("Sending SMS message"); } }
We may change the Producer method in order to support the Polymorphism:
package com.byteslounge.bean; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Produces; import com.byteslounge.bean.impl.EmailMessageSender; import com.byteslounge.bean.impl.SmsMessageSender; @SessionScoped public class MessageSenderFactory implements Serializable { private static final long serialVersionUID = 5269302440619391616L; private MessageTransportType messageTransportType; @Produces public MessageSender getMessageSender() { switch (messageTransportType) { case EMAIL: return new EmailMessageSender(); case SMS: default: return new SmsMessageSender(); } } }
Polymorphism through qualifiers
We may also use qualifiers in order to support polymorphism. Let's see the MessageTransportType enum we used in the previous section:
package com.byteslounge.bean; public enum MessageTransportType { EMAIL, SMS; }
Now we define a new CDI qualifier as a Java annotation:
package com.byteslounge.bean; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RUNTIME) @Target({FIELD, TYPE, METHOD}) public @interface MessageTransport { MessageTransportType value(); }
The qualifier has a property of type MessageTransportType that will be used to disambiguate between service implementations.
Finally we change the Producer in order to use the qualifier:
package com.byteslounge.bean; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Produces; import com.byteslounge.bean.impl.EmailMessageSender; import com.byteslounge.bean.impl.SmsMessageSender; @SessionScoped public class MessageSenderFactory implements Serializable { private static final long serialVersionUID = 5269302440619391616L; @Produces @MessageTransport(MessageTransportType.EMAIL) public MessageSender getEmailMessageSender(){ return new EmailMessageSender(); } @Produces @MessageTransport(MessageTransportType.SMS) public MessageSender getSmsMessageSender(){ return new SmsMessageSender(); } }
Each Producer method will return a distinct message service implementation depending on the qualifier used in the injection point.
Now when we inject the message sending service we may choose which implementation to use by the means of the CDI qualifier:
// Inject the email message sending service @Inject @MessageTransport(MessageTransportType.EMAIL) private MessageSender messageSender;
// Inject the SMS message sending service @Inject @MessageTransport(MessageTransportType.SMS) private MessageSender messageSender;
Producing CDI managed beans
What if we need to Produce CDI managed beans, ie. beans which initialization process should be handled by CDI itself? For example, producing a bean that also has its own CDI dependencies that should be processed by the container during initialization.
We may use injection parameters in the producer method:
@Produces public MessageSender getEmailMessageSender( EmailMessageSender emailMessageSender){ return emailMessageSender; }
When we inject a MessageSender implementation in our application the CDI container will initialize a EmailMessageSender instance and inject it into the Producer method as a parameter. Then the method will return the new service instance initialized by CDI.
Producer method scopes
Producer methods should be seen as independent beans, ie. they also have their own scope. They do not inherit the scope of the bean where they are defined!
By default a Producer method has Dependent scope. This means that every time we fetch an instance produced by some Producer method, the method will always be called. What happens if we change the Producer method scope?
@Produces @SessionScoped public MessageSender getEmailMessageSender( EmailMessageSender emailMessageSender){ return emailMessageSender; }
Now we defined our Producer method as being SessionScoped. This means that only one method execution will ever be executed per HTTP session. The returned instance - EmailMessageSender - will also be SessionScoped.
What if we are injecting some bean into the Producer that has a distinct scope from the Producer itself?
@Produces @SessionScoped public SomeService getService(SomeService someService){ return someService; }
If SomeService parameter is a bean defined - for example - as RequestScoped it would be promoted to SessionScoped by the Producer method and then returned to the caller. This is a problem!
The SomeService instance will be destroyed as soon as the current HTTP request finishes and CDI will then have a broken reference to SomeService - supposedly SessionScoped - instance and consequently provide unpredictable results to further SomeService injections.
We could change the scope of SomeService bean but that would affect all other consumers of the bean! Another approach is to use the New qualifier:
@Produces @SessionScoped public SomeService getService(@New SomeService someService){ return someService; }
With the New qualifier CDI will always inject a new Dependent instance into the Producer method that will then be safely promoted to the Producer scope - in this case session scope - which in turn will return it to the caller.
Disposing
There is also the case where we may need to clean up the produced resources. Further information on resource disposing is available in the following tutorial : Java EE CDI Disposer methods example.
At the end of this page is a downloadable sample that runs on Tomcat and shows Producer polymorphism support through qualifiers. The example consists in the injection of the message sending service in a servlet.