SeamFramework.orgCommunity Documentation
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
MessageDispatcher
DispatcherPolicy
MessageSystemConfiguration
We want to be able to create as many MessageQueue
configurations's 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 Seam 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 Seam XML). 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 Seam XML:
<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 name();
}
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 enqueing messages. The client can then tweak the policy should they wish.
@Generic(ACMEQueue.class)
class MessageManager {
@Inject @Generic
MessageQueue queue;
@Produces @ApplyScope
MessageDispatcher messageDispatcherProducer() {
return queue.createMessageDispatcher();
}
@Produces
DispatcherPolicy getPolicy() {
return queue.getDispatcherPolicy();
}
}