by Adam Bien

EJB 3: From legacy technology to secret weapon

opinion
Oct 28, 200823 mins

Four factors that streamline and modernize EJB 3 development

Some say Enterprise JavaBeans earned its reputation for complexity one burned out programmer at a time. But developers today have begun to notice that EJB 3 is an entirely different animal. Convention over configuration, annotations, dependency injection, and aspect orientation make modern EJBs a lean alternative to XML-laden and JAR-heavy frameworks. In this article, Adam Bien explains how EJB 3’s new component model and persistence handling help streamline enterprise development — and even work well for small and midrange applications.

Enterprise JavaBeans (EJB) technology is known for being neither lightweight nor cool. Until the advent of EJB 3.0, this reputation was certainly warranted. Now, though, EJB technology is lean and sound. EJBs are lighter — even weightless. They no longer require XML configuration, deployment of additional frameworks, or JARs. They integrate well with the Java Persistence API (JPA); scale well on multicore, multi-CPU machines; and are in fact the only vendor-neutral and portable solution for enterprise server-side applications.

Alternative frameworks and solutions, on the other hand, sometimes turn out to be bloated. They can require countless JARs to be configured with several pages of XML and deployed to a Web container that, however “lightweight” it might be, ends up hosting a heavyweight application.

For all of these reasons EJB 3 is worth a second look. Let’s consider what has changed about Enterprise JavaBeans.

EJB then and now

No doubt about it: the EJB 2.1 specification was not concise. It severely violated the DRY (Don’t Repeat Yourself) principle. Information about an EJB component was spread across its remote interface, home interface, bean class, and deployment descriptor. Without tools, refactoring was unproductive and tedious — and IDEs in that era weren’t particularly good at refactoring.

Developers incurred considerable overhead building even simple functionality. The bean class had to implement the javax.ejb.SessionBean interface and was forced to implement all the life-cycle methods. ejbActivate and ejbPassivate were invoked only in stateful session beans, but most deployed session beans were stateless.

For example, in Listing 1, the one and only “business” method — sayHello — is just a fraction of the overall code for HelloWorldBean.

Listing 1. Legacy EJB 2 session bean

public class HelloWorldBean implements SessionBean {
public void setSessionContext(SessionContext aContext) { }
public void ejbCreate() {}
public void ejbActivate() {} //SFSB only
public void ejbPassivate() {}//SFSB only
public void ejbRemove() {}

<b>public void sayHello() {
System.out.println("Hello!");
}</b>
}

The sayHello method had to be declared in an interface too, as in Listing 2:

Listing 2. EJB 2 remote interface

public interface HelloWorldRemote extends EJBObject {
void sayHello() throws RemoteException;
}

The interface had to throw a checked RemoteException. Even so, the bean did not implement the so-called remote interface. This was EJB 1.x/2.x’s main flaw from a maintenance perspective. Type and signature checks were performed during deployment, not at compilation time. Deployment errors were hard to find, which significantly increased round-trip times. You had to provide a deployment descriptor in addition to the code and keep in sync with the code. The XML deployment descriptor was the glue for all the disparate parts, so most of the information already contained in the code was repeated in the descriptor.

Although EJB 2.x session beans were verbose, and the programming harder than necessary, the benefits were huge. Remoting, the single-threaded programming model, state management, transactions, and concurrency control came for “free.” These benefits are all less important (and less interesting) for development than for production, however, which is one reason why developers didn’t really care.

EJB 3.0

From a programming-model point of view, EJB 3 is revolutionary, not evolutionary. The EJB 3 specification is similar to EJB 2.1 behind the scenes, but the changes in the component model and persistence are huge.

To operate, EJB 3.0 beans still need the same amount of information as their EJB 2.1 predecessors. But the way they derive and leverage the information has changed dramatically, and even inverted. Instead of configuring the obvious, you must specify only the deviations from the default. The default case contains metadata — such as class name, method names, attribute names, and suitable conventions — that is sufficient for about 80 percent of all cases. You can fulfill specific requirements by using annotations for configuration, or even by overriding the defaults with XML.

Convention over configuration, or configuration by exception

The principle of convention over configuration is simple: suitable convention is preferable to fine-grained configuration. (The EJB 3 Expert Group didn’t invent convention over configuration, by the way; it borrowed it from Ruby on Rails.) But convention alone is not enough. Essential information, such as the name of the class, is still needed, but it’s derived from the class itself using reflection and doesn’t need to be configured. This is the main difference between EJB 3 and Java 2 Enterprise Edition (J2EE) and other frameworks from its era: With EJB 3, plain classes with only a few annotations have replaced huge deployment descriptors and XML configuration.

Annotations

A subset of the information stored in EJB 2.1 deployment descriptors is available now as Java 5 annotations. The EJB 3.x and JPA specifications rely heavily on them. Annotations are supplemented by metadata derived via reflection from the bytecode and by suitable defaults. The introduction of annotations has dramatically improved the metadata’s usability. Now the compiler checks for the availability of default values and for correct typing, so you needn’t rely on the Javadoc and the deployment process to implement correct syntax. No additional tools or specific IDE extensions are necessary for this purpose.

The annotations introduced with Java EE 5 are lean and explicit. You just “mark” an ordinary Java class (a plain old Java object, or POJO) with the @Stateless annotation to make it an EJB, as in Listing 3.

Listing 3. EJB 3 annotated POJO

@Stateless
public class HelloBean {
 }

The container recognizes HelloBean as a stateless session bean and applies behavior — transactions, state, security aspects, and remoting — that it derives from conventions or that you configure.

I’ve explicitly configured HelloBean‘s stateless nature with the @Stateless annotation. But you do not need to configure transactions explicitly. The default @TransactionAttribute(TransactionAttributeType.REQUIRED) annotation is also applied, although it wasn’t specified. In the default case, every method in a transaction is executed. If a transaction is already active, it is reused; if no transaction exists yet, it is started for you. You can specify this annotation explicitly, override the default with another setting, or rely on the default.

Even the implementation of the interface is no longer needed. All public methods are automatically exposed to the client (the bean’s user). The client acquires an instance of the bean not via a Java Naming and Directory Interface (JNDI) lookup, but using dependency injection.

Dependency injection

Dependency injection (DI) is a fancy term for a simple concept: someone or something (the EJB 3 container in this case) manages dependencies for you. You just declare the need for it and trust the mechanism. Instead of performing lookups, using factories, or even writing custom code, you simply declare the need for a resource.

For example, you no longer need to write custom code like this, as you did for EJB 2.x:

Service service = ServiceFactory.getInstance().createService();

In this custom code, the factory encapsulates the decision as to which implementation of the Service interface will be injected. You had full control over the creation and configuration of the product, which was both an advantage and a burden. Hard-coding the product creation isn’t flexible, so the product was often created via reflection. It was typical to externalize the fully qualified name and store it in property or XML files.

Listing 4 is another example of custom code that’s no longer necessary:

Listing 4. Manual EJB 1.x/2.x lookup implementation

try{
Context ctx = new InitialContext();
Object proxy = ctx.lookup("service");
ServiceHome home = (ServiceHome)PortableRemoteObject.narrow(proxy,ServiceHome.class);
Service service = home.create();
}catch(Exception ex){ /* omitted */}

This a lookup of an EJB 2.X bean. Although the bean didn’t implement the interface directly, a particular bean class was specified in a deployment descriptor (an XML file again), and so was replaceable as well. The amount of configuration was massive and difficult to maintain. Many frameworks from that time were highly configurable in XML. The configuration rarely paid off, because the possibly replaceable classes were actually never replaced.

With EJB 3.x, you just declare the dependency and the need for automatic association with a given instance, as in Listing 5.

Listing 5. Declarative injection in EJB 3.x

@EJB
private Service service;

The container does the work. It scans for the annotation and injects the implementation, before a business method is invoked. The process of acquiring an implementation of an interface and associating it with a field is completely factored out from the application code, making it leaner and less error-prone.

If there’s only one implementation of a given bean interface to inject, you don’t need to specify it; convention over configuration kicks in again. When you have more than one implementation, the container must differentiate among the possibilities. Pure convention isn’t enough; you must provide at least a hint about which class to use.

In Listing 6, for example, the container injects ClientBean, an implementation of the Service interface:

Listing 6. Injecting the default

@Stateless
public class ClientBean implements Client {
@EJB
private Service service;

public String getHello() {
return this.service.getMessage();
}
}

For demonstration purposes, Listing 7 introduces more than one implementation of the interface so that the convention is explicitly violated. The two implementations return different messages but are adequate for this example.

Listing 7. Different implementations of the same interface: Beyond the convention

@Stateless
public class DefaultService implements Service {
public String getMessage() {
return "Hello from: " + this.getClass().getName();
}
}

@Stateless
public class SpecificService implements Service {
public String getMessage() {
return "Hello from: " + this.getClass().getName();
}
}

The EJB’s name is derived from the simple class name (not the fully qualified name). So an attempt is made to deploy two different beans — DefaultService and SpecificService The deployment fails at an early stage with the following error:

Cannot resolve reference Unresolved Ejb-Ref
com.abien.samples.di.client.ClientBean/service@jndi:
@null@com.abien.samples.di.service.Service@Session@null because there are 2 ejbs in the
application with interface com.abien.samples.di.service.Service

You can solve the problem either by hard-coding the dependency with annotation attributes, or by using the optional ejb-jar.xml descriptor.

Hard-coding dependencies

Hard-coding the dependency is simple. You need only set the attribute in the @EJB annotation and specify the ejb-name, as in Listing 8.

Listing 8. Configuring the nonobvious, with annotations

public class ClientBean implements Client {

@EJB(beanName="DefaultService")
private Service service;

Using the ejb-jar.xml descriptor

Using the ejb-jar.xml descriptor is more labor-intensive. Instead of changing the code, you must resolve the dependency in the deployment descriptor, as in Listing 9.

Listing 9. Resolving the reference in the XML deployment descriptor

<ejb-jar ...>
   <enterprise-beans>
      <session>
         <ejb-name>ClientBean</ejb-name>
            <ejb-local-ref>
               <ejb-ref-name>...di.client.ClientBean/service</ejb-ref-name>
                  <local>com.abien.samples.di.service.Service</local>
                  <ejb-link>DefaultService</ejb-link>
            </ejb-local-ref>
      </session>
   </enterprise-beans>
</ejb-jar>

This approach is more flexible and allows the resolution of the dependency to happen externally, without code modification. The declaration of the dependency remains unchanged; you don’t need to provide any additional attributes. It is more suitable for testing purposes or product customization.

You can even override existing annotations with the XML deployment descriptor. This capability is especially convenient for providing a specific configuration for integration testing. A “production ready” configuration that uses annotations can be still overridden with ejb-jar.xml on demand. Then you only need to provide the deployment descriptor in the integration-testing phase and make sure you remove it for production.

You can apply similar principles for injection of resources such as Java Message Service (JMS) Destinations and ConnectionFactories: you can configure the DI either with annotations or in deployment descriptors. (I’ll get back to resource injection a little later on.) DI for persistence, though, requires an additional file.

Persistence implementation

For persistence, you need a JPA deployment descriptor named persistence.xml. Injecting the EntityManager without specifying the persistence unit works only if a single persistence unit is specified in persistence.xml, as in Listing 10.

Listing 10. Injection of EntityManager from the (one and only) default persistence unit

@Stateless
public class BookServiceBean implements BookService {

/*no additional information ...works only for one persistence unit in the persistence.xml*/
@PersistenceContext
private EntityManager em;

public void create(Book book) {
this.em.persist(book);
}
}

In all other cases the reference to the EntityManager must be resolved, either with an attribute of the annotation or in the deployment descriptor. It is a common case to have more than one persistence unit with different configurations. Every injected EntityManager is related to a persistence unit and is independent of the others. Different EntityManagers:

  • Have distinct caches
  • Can be configured differently in persistence.xml
  • Can be used for the realization of different use cases categories

An EntityManager responsible for access to the master data could use aggressive caching, while the EntityManager for the operational data could be configured with a. “weak” reference cache. The one responsible for export/import could be deployed with no cache activated at all.

JPA can be used outside the container as well. This feature is especially useful for testing purposes. However, it requires another persistence unit with dedicated a JDBC driver configuration and RESOURCE_LOCAL transaction handling — another example of several persistence units inside a persistence.xml deployment descriptor. In the example in Listing 11, both persistence units are identical, except for the database schema generation. The DaC persistence unit drops and creates all tables on deploy and undeploy, respectively. The None persistence unit just relies on the existence of the schema.

Listing 11. Configuration of different persistence units

<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
   <persistence-unit name="DaC" transaction-type="JTA">
      <provider>oracle.toplink.essentials.PersistenceProvider</provider>
         <jta-data-source>jdbc/sample</jta-data-source>
            <properties>
               <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
            </properties>
   </persistence-unit>

   <persistence-unit name="None" transaction-type="JTA">
      <provider>oracle.toplink.essentials.PersistenceProvider</provider>
         <jta-data-source>jdbc/sample</jta-data-source>
            <properties>
            </properties>
   </persistence-unit>
</persistence>

Analogous to EJB injection, injection of the EntityManager must be explicitly configured if it is arbitrary and can’t be inferred from the convention. The most flexible — but also most tedious — way to resolve the dependency is in the deployment descriptor, as in Listing 12.

Listing 12. Resolving the ambiguity in XML

<ejb-jar ...>
   <enterprise-beans>
      <session>
         <ejb-name>BookServiceXMLBean</ejb-name>
            <persistence-context-ref>
               <persistence-context-ref-name>...di.persistence.BookServiceXMLBean/em</persistence-context-ref-name>
               <persistence-unit-name>DaC</persistence-unit-name>
            </persistence-context-ref>
      </session>
   </enterprise-beans>
</ejb-jar>

You can maintain the relation between the injected EntityManager and its persistence unit without recompiling the session bean, which is especially useful for local tests. On the other hand, the deployment descriptor is an additional artifact that you must create and maintain, so this approach is not free. Refactoring, especially, can be challenging, because the IDE must keep the source and XML configuration consistent.

For static resolution, annotations are more appropriate and efficient. You can easily set the persistence unit’s name in the @PersistenceContext annotation, as in Listing 13. No additional deployment descriptors are required for this purpose.

Listing 13. Specifying the unit name with annotations.

@Stateless
public class BookServiceAnnotationBean implements BookService {

@PersistenceContext(unitName="DaC")
private EntityManager em;

public void create(Book book) {
this.em.persist(book);
}
}

Injecting resources

The injection of resources works slightly differently, although the principle is the same. DataSources, JMS resources, or mail Sessions are injected using the @Resource annotation, as shown in Listing 14.

Listing 14. Injecting resources from JNDI

@Stateless
public class DataSourceInjectionBean implements DataSourceInjection {
@Resource(mappedName = "mail/Context")
private Session mailContext;

@Resource(mappedName = "jms/Queue")
private Queue queue;
@Resource(mappedName = "jms/ConnectionFactory")
private ConnectionFactory eocFactory;

@Resource(name="jdbc/sample")
private DataSource ds;

public void accessResources(){
//use the datasource
}

}

The deployed resources are uniquely identified by the JNDI name, which can be used for injection, instead of “traditional” lookup. You can use the mappedName attribute for this purpose, although it is just a suggestion in the EJB specification and not mandatory behavior.

You can introduce additional indirection with the name attribute (see the DataSource in Listing 14), which can be resolved later in the application server specific deployment descriptor. Especially in this case, remember to “keep it simple.” Every indirection costs you additional effort, which can be overwhelming at the end. Just think of all the overconfigured J2EE projects you’ve seen.

Interceptors

EJB 3 also gives you a way to do lightweight aspect-oriented programming (AOP). The principle of AOP is as simple as the principle of DI. The idea behind AOP is strict separation between pure business logic and nonfunctional code. You provide the nonfunctional code — the aspects — once and reuse it in different places. The reuse, however, happens externally, in the sense that the business-logic classes do not even know that they are decorated with additional functionality. In fact AOP could be explained as a flexible and configurable decorator that can be applied to methods, attributes, or constructors in declarative manner.

The EJB container comes with some built-in, highly reusable aspects. The availability of remote EJBs via SOAP-based Web services, RESTful services, or even Internet Inter-ORB Protocol (IIOP) remoting is nothing other than decoration of existing functionality with the crosscutting aspect of “remoting.” Transaction and exception handling, security, concurrency, state management, and even persistence are classical aspects that the J2EE platform has supported from the beginning — long before the term AOP became popular. Application-defined aspects were introduced with the EJB 3.0 specification. Before then, only the Web container could intercept incoming requests with the servlet filter.

In a well-designed Java EE application, session beans implement the major portion of business logic. This logic is functional or even procedural. Session beans are the natural place for the use of AOP ideas. Session beans mainly consist of methods and a default constructor. Attributes are very rare and mostly used in stateful session beans. All other attributes are not client specific and are shared by all instances. References to EntityManager, JMS resources, or other EJBs are typical examples for such technical state.

The Java EE aspects are called interceptors. Comparing them to fully fledged AOP frameworks is not accurate, because they are optimized for use with EJBs and so are rather limited. An EJB interceptor can be applied only to a particular EJB’s methods. You can’t apply interceptors to constructor invocations or attribute access. Interceptors are nevertheless absolutely sufficient for the most common use cases. You can enable an interceptor either with an annotation or in a deployment descriptor. (In this respect, interceptors are conceptually identical to the DI approach.)

An EJB 3 interceptor is just a Java class with one annotated method, as in Listing 15:

Listing 15. A tracing interceptor

public class TracingInterceptor {
@AroundInvoke
public Object trace(InvocationContext invocationContext) throws Exception{
System.out.println("Method: " + invocationContext.getMethod());
return invocationContext.proceed();
}
}

The actual call to the EJB is performed after the interceptor’s execution. The interceptor has full control of the execution. It can invoke the method several times, change the parameters and return values, transform exceptions, and so on. The implementation of the interceptor is independent of the business logic. You activate an interceptor for a particular EJB by using the @Interceptors annotation, as in Listing 16:

Listing 16. Declaring an interceptor using an annotation

@Stateless
@Interceptors({TracingInterceptor.class, PerformanceMeasurement.class})
public class HelloBean implements Hello {

public String sayHello() {
return "Hello from Bean";
}
}

The annotation’s value is an array of classes. The array’s order is equivalent to the interception order.

Declaration of interceptors with annotations is convenient because it can fully leverage IDE support. Because the @Interceptors annotation refers to the interceptors as classes and not as Strings, your IDE and the compiler provide a higher comfort level by ensuring consistency and providing auto-completion.

On the other hand, because the interceptors are declared in the business code, every change requires recompilation and redeployment of the whole application. This could become a problem for “utility” interceptors that are only needed in the development phase or only activated for troubleshooting.

Fortunately, you can declare and apply interceptors in the standard deployment descriptors as well, as in Listing 17:

Listing 17. Declaring an interceptor in the XML deployment descriptor

<ejb-jar ...>
<!-- declaration (needed once) -->
   <interceptors>
      <interceptor>
         <interceptor-class>...PerformanceMeasurement</interceptor-class>
      </interceptor>
   </interceptors>

<!-- interceptor activation -->
   <assembly-descriptor>
      <interceptor-binding>
         <ejb-name>HelloBean</ejb-name>
            <interceptor-order>
               <interceptor-class>...PerformanceMeasurement</interceptor-class>
            </interceptor-order>
      </interceptor-binding>
   </assembly-descriptor>
</ejb-jar>

The first part of the configuration in Listing 17 is the actual declaration of the interceptor. It is needed only once. In the second part, the already declared interceptor can be applied to all beans from the ejb-jar, a particular bean, or even a specific, overloaded method.

The XML configuration is not only more flexible but also more powerful. Only in the deployment descriptor can you apply “default” interceptors that can intercept all beans in the module. But you should have additional good reasons for introducing XML deployment descriptors. They are harder to create and maintain. The XML configuration must be versioned together with source code; it’s equally important and must be maintained with the same care. And although the application is more flexible and configurable with externalized XML configuration, this advantage loses its importance after deployment. No one will change the XML configuration in production without a test phase, so the additional flexibility is useful only in the development phase.

Another argument for the annotation-driven approach is the lower amount of “magic.” The relation between the main concern (the session bean) and the cross-cutting concern (the interceptor) is self-documented. Refactoring of the interceptor — such as renaming or moving it to other package — can be performed in any average IDE without specific Java EE or EJB support.

Versatility of interceptors

Interceptors are not just POJOs; they come with some interesting features. They participate in transactions and support DI. So an interceptor can even use the functionality of another EJB directly, via DI, as the auditing code in Listing 18 does.

Listing 18. Injecting an EJB into an interceptor

public class AuditInterceptor {

@EJB
private Audit audit;

@AroundInvoke
public Object trace(InvocationContext invocationContext) throws Exception{
String info = ("Method: " + invocationContext.getMethod() + "n");
info += ("Object: " + invocationContext.getTarget().getClass().getName());
this.audit.audit(info);
return invocationContext.proceed();
}
}

This feature makes it convenient to access an EJB’s existing logic with the services it provides, such as transactions, concurrency, or security. This DI itself can be configured in the same manner: with annotations, with XML, with both combined, or by relying on convention and using the default implementation. Audit is a ubiquitous and rather trivial example for AOP. Interceptors can be used for a variety of other real-world purposes. Some ideas include:

Interceptors and AOP

Interceptors help you to realize nonfunctional requirements without touching the business code. They aren’t as powerful as fully-fledged AOP languages but are sufficient for most use cases. AOP and interceptors in the real world are less common than you might think. The Java EE container comes with a bunch of useful aspects that can be enabled declaratively without any coding. Java EE developers don’t even need to know that persistence, transactions, concurrency, or conversational state are actually aspects.

  • Ensuring preconditions: Interceptors have access to the intercepted EJB — not only to the methods, but to their parameters with annotations as well. It is relatively easy to evaluate the annotations and ensure the parameters’ validity. For example, the annotation @NotNull already implies that the method should be invoked with a non-null parameter. With this approach, validation of the preconditions could be easily moved from the EJB into a reusable interceptor.
  • Ensuring service level agreements (SLAs):This case is similar to ensuring the preconditions. You could measure the performance of a method and escalate all too-slow invocations using, for example, a JMS topic wrapped in a session bean. This lightweight monitoring capability is especially important in service-oriented architecture (SOA) environments, where multiple clients must rely on the availability of a single, reusable service.
  • Enhancing DI capabilities: An interceptor can access the intercepted EJB instance directly, so it can access its fields and search for annotations. Additional values can be injected directly into a session bean’s fields or methods. The Seam framework uses this approach. Integration with Google Guice can be approached in this way too.
  • Transforming and filtering exceptions: An interceptor invokes a method of the next participant in the chain. It wraps the invocation completely, so it can catch any encountered exceptions, swallow them, or rethrow clean versions. This is particular useful for dealing with legacy connectors with their own exceptions in the cause. (Not all exceptions, however, can be easily caught in an interceptor. Some exceptions, for example the javax.persistence.OptimistickLockException, might occur at the end of transaction. Because the interceptors are involved in the same transactions, it’s impossible for them to catch such exceptions.)

In conclusion

EJB 3 — especially EJB 3.1, with its support for no-interface views, WAR deployment, and singletons — is even interesting for small and midrange applications. It is so streamlined that it’s hard to slim it down any further. I don’t know of any other framework that can implement simple CRUD functionality with less overhead than what you see in Listing 19.

Listing 19. CRUD with EJB 3.1 and without a business interface

@Stateless
public class BookMmgr{

    @PersistenceContext
    private EntityManager em;

    public void create(Book t) {
        this.em.persist(t);
    }

    public Book find(String id){
       return this.em.find(Book.class, id);
    }

    public void delete(Book t) {
       t = this.em.merge(t);
       this.em.remove(t);
    }

    public Book update(Book t) {
        return this.em.merge(t);
    }
}

Note that this example requires no XML. Only the EntityManager needs a few lines of configuration in the persistence.xml file.

New design patterns emerge from the synergy that comes from using EJB 3 with JPA entities. Even better, EJB 3 lets you do without most of the J2EE “best practices” that made that framework so complex to use.

See the Resources section to learn more about EJB technology then and now.

Consultant and author Adam Bien is an Expert Group member for the Java EE 6, EJB 3.1, and JPA 2.0 JSRs; a NetBeans Dream Team Member; and a Java Champion. He has edited several books about Java and J2EE technology. He is an architect and developer on Java EE 5 projects and participates in several open source projects as committer and owner. He is working on Productive Java EE – Rethinking the Best Practices, a new book for O’Reilly Media. Visit Adam’s blog.