EJB 3: From legacy technology to secret weapon

Four factors that streamline and modernize EJB 3 development

1 2 3 4 5 6 Page 3
Page 3 of 6

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.

1 2 3 4 5 6 Page 3
Page 3 of 6