SeamFramework.orgCommunity Documentation

Chapter 15. Generic Beans

15.1. Using generic beans
15.2. Defining Generic Beans

Many common services and API's require the use of more than just one class. When exposing these services via CDI, it would be time consuming and error prone to force the end developer to provide producers for all the different classes required. Generic beans provide a solution, allowing a framework author to provide a set of related beans, one for each single configuration point defined by the end developer. The configuration points specifies the qualifiers which are inherited by all beans in the set.

To illustrate the use of generic beans, we'll use the following example. Imagine we are writing an extension to integrate our custom messaging solution "ACME Messaging" with CDI. The ACME Messaging API for sending messages consists of several interfaces:

MessageQueue

The message queue, onto which messages can be placed, and acted upon by ACME Messaging

MessageDispatcher

The dispatcher, responsible for placing messages created by the user onto the queue

DispatcherPolicy

The dispatcher policy, which can be used to tweak the dispatch policy by the client

MessageSystemConfiguration

The messaging system configuration

We want to be able to create as many MessageQueue configurations as they need, however we do not want to have to declare each producer and the associated plumbing for every queue. Generic beans are an ideal solution to this problem.

Before we take a look at creating generic beans, let's see how we will use them.

Generic beans are configured via producer methods and fields. We want to create two queues to interact with ACME Messaging, a default queue that is installed with qualifier @Default and a durable queue that has qualifier @Durable:

class MyMessageQueues {

   
   @Produces
   @ACMEQueue("defaultQueue")
   MessageSystemConfiguration defaultQueue = new MessageSystemConfiguration();
   
   @Produces @Durable @ConversationScoped
   @ACMEQueue("durableQueue")
   MessageSystemConfiguration producerDefaultQueue() {
      MessageSystemConfiguration config = new MessageSystemConfiguration();
      config.setDurable(true);
      return config;
   }
}

Looking first at the default queue, in addition to the @Produces annotation, the generic configuration annotation ACMEQueue, is used, which defines this to be a generic configuration point for ACME messaging (and cause a whole set of beans to be created, exposing for example the dispatcher). The generic configuration annotation specifies the queue name, and the value of the producer field defines the messaging system's configuration (in this case we use all the defaults). As no qualifier is placed on the definition, @Default qualifier is inherited by all beans in the set.

The durable queue is defined as a producer method (as we want to alter the configuration of the queue before having Solder use it). Additionally, it specifies that the generic beans created (that allow for their scope to be overridden) should be placed in the conversation scope. Finally, it specifies that the generic beans created should inherit the qualifier @Durable.

We can now inject our generic beans as normal, using the qualifiers specified on the configuration point:

class MessageLogger {

  
   @Inject
   MessageDispatcher dispatcher;
  
   void logMessage(Payload payload) {
      /* Add metaddata to the message */
      Collection<Header> headers = new ArrayList<Header>();
      ... 
      Message message = new Message(headers, payload);
      dispatcher.send(message);
   }
  
}
class DurableMessageLogger {

  
   @Inject @Durable
   MessageDispatcher dispatcher;
   
   @Inject @Durable
   DispatcherPolicy policy;
   
         
   /* Tweak the dispatch policy to enable duplicate removal */
   @Inject
   void tweakPolicy(@Durable DispatcherPolicy policy) {
      policy.removeDuplicates();
   }
  
   void logMessage(Payload payload) {
      ...
   }
}

It is also possible to configure generic beans using beans by sub-classing the configuration type, or installing another bean of the configuration type through the SPI (e.g. using Solder Config). For example to configure a durable queue via sub-classing:

@Durable @ConversationScoped

@ACMEQueue("durableQueue")
class DurableQueueConfiguration extends MessageSystemConfiguration {
      
   public DurableQueueConfiguration()
   {
      this.durable = true;
   }
}

And the same thing via Solder Config:


<my:MessageSystemConfiguration>
   <my:Durable/>
   <s:ConversationScoped/>
   <my:ACMEQueue>durableQueue</my:ACMEQueue>
   <my:durable>true</my:durable>
</my:MessageSystemConfiguration>

Having seen how we use the generic beans, let's look at how to define them. We start by creating the generic configuration annotation:

@Retention(RUNTIME)

@GenericType(MessageSystemConfiguration.class)
@interface ACMEQueue {
   String value();
   
}

The generic configuration annotation a defines the generic configuration type (in this case MessageSystemConfiguration); the type produced by the generic configuration point must be of this type. Additionally it defines the member name, used to provide the queue name.

Next, we define the queue manager bean. The manager has one producer method, which creates the queue from the configuration:

@GenericConfiguration(ACMEQueue.class) @ApplyScope

class QueueManager {
   @Inject @Generic
   MessageSystemConfiguration systemConfig;
   
   @Inject
   ACMEQueue config;
   
   MessageQueueFactory factory;
   
   @PostConstruct
   void init() {
      factory = systemConfig.createMessageQueueFactory();
   }
   
   @Produces @ApplyScope
   public MessageQueue messageQueueProducer() {
      return factory.createMessageQueue(config.name());
   }
}

The bean is declared to be a generic bean for the @ACMEQueue generic configuration type annotation by placing the @GenericConfiguration annotation on the class. We can inject the generic configuration type using the @Generic qualifier, as well the annotation used to define the queue.

Placing the @ApplyScope annotation on the bean causes it to inherit the scope from the generic configuration point. As creating the queue factory is a heavy operation we don't want to do it more than necessary.

Having created the MessageQueueFactory, we can then expose the queue, obtaining its name from the generic configuration annotation. Additionally, we define the scope of the producer method to be inherited from the generic configuration point by placing the annotation @ApplyScope on the producer method. The producer method automatically inherits the qualifiers specified by the generic configuration point.

Finally we define the message manager, which exposes the message dispatcher, as well as allowing the client to inject an object which exposes the policy the dispatcher will use when queuing messages. The client can then tweak the policy should they wish.

@Generic

class MessageManager {
   @Inject @Generic
   MessageQueue queue;
   
   @Produces @ApplyScope
   MessageDispatcher messageDispatcherProducer() {
      return queue.createMessageDispatcher();
   }
   
   @Produces
   DispatcherPolicy getPolicy() {
      return queue.getDispatcherPolicy();
   }   
   
}