Java CanDI Injection in subetha
Studying the source code for a full application is the best way to really understand a technology like Java Injection (CanDI, JSR-299). Fortunately, Jeff Schnitzer, Scott Hernandez, and Jon Stevens have created a subetha mail, an open-source Java implementation of a mail list manager (like mailman) using CanDI extensively. Because subetha is also a sophisticated JavaEE application using EJB @Stateless beans, JMS queues with EJB @MessageDriven beans, servlets, and Hessian remote services, it’s a great overall application to study.
- Services: CanDI and EJB @Stateless
- Extensions and Plugins
- Initialization with CanDI
- Servlets with CanDI
- Queue message listeners
- XML configuration
- Hessian Services
- Conclusions
- subetha CanDI/XML classes and patterns
Services
When looking at new source, I find it easiest to search for the main services, because the services give a quick roadmap to the architecture. Once I’ve got a mental map of the source, it’s easier to see how classes and functions fit into the design. From reading subetha, CanDI nicely identified the main services because they are either marked with @ApplicationScoped, @Service or @Stateless. Since subetha uses message queues, the two EJB @MessageDriven beans also act as services.
@ApplicationScoped services tend to store runtime state, while @Stateless services generally encapsulate persistence (JPA), database, or other transactional state, and @MessageDriven beans read from queues. Because all of the services are singletons, subetha uses the CanDI @Current injection binding type to wire them up. No XML is required.
A typical example of the subetha wiring is the InjectorBean @Stateless EJB which receives new mail messages, parses them and dispatches them to the inject queue for further processing. The CanDI @Current annotation wires four subetha services: FilterRunner, Encryptor, Detacher, and PostOffice. The subetha InjectorBean also uses CanDI to bind the JMS queue with a custom @BindingType @InjectQueue and a JavaMail session with a binding type @OutbountMTA. Finally, the @SubEtha binding type requests the internal SubEthaEntityManager, a DAO which encapsulates the JPA data store.
@Stateless(name="Injector")
@RolesAllowed(Person.ROLE_ADMIN)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class InjectorBean implements Injector
{
@Current FilterRunner filterRunner;
@Current Encryptor encryptor;
@Current Detacher detacher;
@Current PostOffice postOffice;
@InjectQueue inboundQueue;
@OutboundMTA Session mailSession;
@SubEtha SubEthaEntityManager em;
...
}
This one class illustrates how CanDI encourages a declarative style of Java injection, as well as how EJB 3.0 has simplified. With CanDI, you describe the services and resources needed with the binding types, and use CanDI to bind the services together. The binding types are a small number of custom annotations which specify exactly what resource you want. Because they’re Java annotations, they are documented in JavaDoc and checked by the compiler. Subetha uses 6 binding types in total. When you have a singleton service, you’ll use @Current. When you have multiple resources, like subetha’s queues, you’ll create a descriptive annotation like @InjectQueue.
Like CanDI, the EJB configuration is configured by annotations, without relying on any XML. By keeping all the configuration and binding information to the class, the class becomes self-documenting: you don’t need to read the XML side by side with the classes to understand what’s going on. For InjectBean, the @Stateless annotation is all that’s necessary to declare a stateless EJB. The @TransactionAttribute protects the methods with transactions, and the @RolesAllowed ensures that only admin users can access the InjectBean.
Extensions and Plugins
We all want out software to be flexible and extensible, and CanDI can help creating a simple plugin system. Subetha has two plugin types: Filters and Blueprints, and uses CanDI to discover all extension classes. Because CanDI already scans all the classes, it automatically lets you query existing plugins using the BeanManager class.
class BeanManager {
Set getBeans(Type type. Annotation...bindings);
}
In subetha, the Filters and Blueprints are gathered in a startup service and stored in a FilterRegistry and BlueprintRegistry.
Initialization Services
An interesting use of CanDI in subetha is using a @Startup service for initialization. Since Resin’s CanDI automatically starts any bean marked with @Startup, subetha can declare all its startup in the class itself without relying on any XML configuration.
Servlets
The subetha servlets use CanDI to bind services to the servlets, making servlets look just like CanDI services. An example is the InjectorServlet, which allows POST of new messages and uses the Injector service to do the actual work.
public class InjectorServlet extends HttpServlet
{
@Current AccountMgr accMgr;
@Current Injector inj;
@Current SubEthaLogin resinLogin;
...
}
Message Listeners (MDB)
Subetha uses two queues to store and process messages: an @InjectQueue for inbound messages and a @DeliveryQueue to send messages to JavaMail. To process the messages, subetha has two corresponding @MessageDriven beans, InjectListener and DeliveryListener. Like the services, the message listeners describe the services to use with binding type annotations and rely on CanDI for the binding.
@MessageDriven
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class InjectListener implements MessageListener
{
@DeliveryQueue BlockingQueue outboundQueue;
@SubEtha SubEthaEntityManager em;
public void onMessage(Message qMsg)
...
}
XML configuration
While CanDI eliminates XML for wiring, you still need to configure resources and may need user-customization. For example, databases and JavaMail services need specific configuration. I’ve pulled out two examples from the subetha configuration that illustrate interesting CanDI concepts: configuring a subetha service, and configuring a @BindingType for a standard JMS queue.
subetha has one main configurable service, the SMTPService, which listens for new messages, and which users may want to change the IP address or TCP port. The XML for the service uses the class package and name as <smtp:SMTPService> and configures properties using XML tags.
<web-app xmlns="http://caucho.com/ns/resin"
xmlns:resin="urn:java:com.caucho.resin"
xmlns:smtp="urn:java:org.subethamail.core.smtp">
<smtp:SMTPService>
<port>2500
<bindAddress>192.168.1.10</bindAddress>
<fallbackHost>localhost:2525</fallbackHost>
</smtp:SMTPService>
subetha also uses XML to configure the JMS queues and binds the queues to the subetha custom binding types, @InjectQueue and @DeliverQueue. The configuration looks like the following:
<web-app xmlns="http://caucho.com/ns/resin"
xmlns:resin="urn:java:com.caucho.resin"
xmlns:queue="urn:java:org.subethamail.core.queue"
xmlns:smtp="urn:java:org.subethamail.core.smtp">
<jms:FileQueue>
<queue:InjectQueue/>
<Named>inject</Named>
<jms:FileQueue>
<ejb-message-bean class="org.subethamail.core.queue.InjectListener">
<destination>#{inject}</destination>
<ejb-message-bean>
Hessian web services
Interestingly, subetha has chosen to expose several of its services as Hessian services. Because Hessian works with serializable Java classes (with a zero-arg constructor), the Java code itself is independent of Hessian. The Hessian services are declared in the XML, like servlets:
<web-app xmlns="http://caucho.com/ns/resin">
<servlet-mapping url-pattern="/api/AccountMgr"
servlet-class="org.subethamail.core.acct.AccountMgrBean">
<protocol uri="hessian:"/>
</servlet-mapping>
</web-app>
Conclusions
Since one of the major difficulties with any new technology like CanDI is discovering the useful patterns (and anti-patterns), having a well-written, complete example to study is invaluable. Subetha, along with being well-written and designed, is a particularly good example because it intelligently uses the bulk of the important JavaEE 6 technologies: CanDI, EJB @Stateless beans, Servlets, JSP, JMS queues and EJB MDB beans, as well as commonly used technologies like Velocity and Hessian web services.
For me, it’s also nice to look through some code showing how CanDI will be used in practice, because my perspective as an implementer of CanDI is different from people actually using the spec. In particular, it was good to see the advantages of the declarative injection style with self-documenting annotations actually work out in practice. Plus, as something of a surprise, to see the importance of the @Startup services and to see the CanDI plugin capabilities used in one of the first full CanDI applications.
subetha CanDI/EJB classes and patterns
Patterns
- Singleton state services
- Startup beans
- EJB session bean managers
- Extension beans
- Servlets
- Scheduled tasks
- Named resources
- Queues - JMS using BlockingQueue and EJB message-driven beans.
BindingTypes
- @Current - standard bindings used for most services
- @OutboundMTA - SMTP Session resource
- @SubEtha - JPA EntityManager
- @DeliveryQueue - JMS queue
- @InjectQueue - JMS queue
- @Name - used for Resin resources (should be changed)
Service Beans (@ApplicationScoped and @Service)
- PostfixTcpTableService - incoming connection service
- EncryptorBean - encryption/decription service
- FilterRunnerBean - runs filters on message
- ListWizardBean - creates a list from a Blueprint
- SMTPService - wrapper around SMTPServer for main outgoing msgs
Init/Startup Beans
- BootstrapperBean - used for a fresh install
- ScannerService - scans for blueprints and filters on startup
- VelocityService - velocity initialization on startup
Servlets
- Backend - repository for services
- ArchiveServlet
- ExportServlet
- InjectorServlet
- ListServlet
Scheduled Tasks
- IndexerBean
- CleanupBean
Stateless Beans
- AccountMgrBean - updates a user record
- AdminBean - subscribe/unsubscribe
- DeliveratorBean - delivers messages
- DetacherBean - multipart-mime
- InjectorBean - parses incoming and sends to injector queue
- ArchiverBean - access to the message archive
- ListMgrBean - mailing list management
- PostOfficeBean - sends system mail messages to users
Queues and Message Driven Beans
- @InjectQueue - inbound messages
- @DeliveryQueue - outbound messages
- DeliveryListener
- InjectListener
Hessian
- AccountMgrBean
- AdminBean
- ListWizardBean
- InjectorBean
- ArchiverBean
- ListMgrBean
- IndexerBean
- EegorBringMeAnotherBrainBean
