본문 바로가기

카테고리 없음

JavaWorld-Simplify enterprise Java development with EJB 3.0, Part 1

This story appeared on JavaWorld at
http://www.javaworld.com/javaworld/jw-08-2005/jw-0815-ejb3.html

Simplify enterprise Java development with EJB 3.0, Part 1

Use annotations to develop POJO services

Java Enterprise Edition, or Java EE (previously called J2EE), is a powerful but notoriously complex platform for developing server-side applications. Since its early days, complexity has long been cited as one of the major factors holding back Java EE's adoption. In a previous JavaWorld article "On the Road to Simplicity," I discussed some of those issues that complicate Java EE application development, specifically those related to the current Enterprise JavaBeans (EJB) 2.1 specification.

Over the past three years, the Java open source community, the Java Community Process (JCP), and major Java EE vendors have endeavored to make Java EE simpler. For example, new design paradigms, such as POJO (plain-old Java object) services, service interceptors, and dependency injection, are practiced in real-world applications to simplify Java EE development. And new tools and frameworks, such as Hibernate, AOP (aspect-oriented programming), Struts, XDoclet, and Spring, have been widely adopted for the same purpose.

Simplification is not feature reduction
Simplifying a programming model does not reduce its functionality. Simplification merely hides the complex logic behind framework code or reusable components. In essence, it shifts complexity from one part of the system that needs explicit management by application developers to another part invisible to most developers.


Those patterns and tools have made life easier for beginning developers while improving the productivity for experienced Java developers and are currently being incorporated into the next generation of Java EE standards (i.e., EJB 3.0) by the JCP. A recent study by Java developer Raghu Kodali has shown that porting Sun's Java EE tutorial application RosterApp from EJB 2.1 to EJB 3.0 resulted in more than a 50-percent reduction in code.

Java annotations are the key behind EJB 3.0, which ties POJO services, POJO persistence, and dependency injection altogether into a complete enterprise middleware solution. In this article, I use a sample application, the JBoss EJB 3.0 TrailBlazer, to illustrate how to develop lightweight EJB 3.0 POJO applications with annotations. The TrailBlazer application implements an investment calculator multiple times using different tools and APIs available in EJB 3.0. The sample application runs out of the box in JBoss Application Server 4.0.3 and is fully compliant to the latest EJB 3.0 specification (public review) at the time of this writing.

Let's begin by examining the benefits of an annotation-driven programming model.

EJB 3.0's annotation-driven programming model

From the developer's point of view, EJB 3.0 extensively uses Java annotations. Annotations have two key advantages: they replace excessive XML-based configuration files and eliminate the need for the rigid component models.

Annotations versus XML

XML-based deployment descriptors and annotations can both be used to configure service-related attributes in Java EE applications. The two differ in that XML files are processed separately from the code, often at runtime, while annotations are compiled with the code and checked by the compiler. That has some important implications for developers, as I list below:

  • Verbosity: XML configuration files are known for their verbosity. To configure the code, XML files must duplicate a lot of information, such as class names and method names, from the code. Java annotations, on the other hand, are part of the code and specify configuration information without external reference to the code.
  • Robustness: The duplicated code information in the XML configuration files introduces multiple points of potential failures. For example, if you misspell a method name in the XML file, the application would fail at runtime. In other words, XML configuration files are less robust than annotations, which are checked by the compiler and processed with the rest of the code.
  • Flexibility: Since the XML files are processed separately from the code, the XML-based configuration data is not "hard-coded" and can be changed later. The deploy time flexibility is a great feature for system administrators.

Annotations are easy to use and prove sufficient for most application needs. XML files are more complex and can be used to address more advanced issues. EJB 3.0 allows you to configure most application settings via annotations. EJB 3.0 also supports XML files for overriding default annotation values and configuring external resources such as database connections.

In addition to replacing and simplifying XML descriptors, annotations also allow us to do away with the rigid component model that plagued EJB 1.x and 2.x.

POJO versus rigid components

EJB components are container-managed objects. The container manipulates the behavior and internal state of the bean instances at runtime. For this behavior to occur, the EJB 2.1 specification defines a rigid component model the beans must conform to. Each EJB class must inherit from a certain abstract class that provides callback hooks into the container. Since Java supports only single inheritance, the rigid component model limits a developer's ability to build complex object structure using EJB components. That is especially a problem for mapping complex application data in entity beans, as you will see in Part 2 of this series.

In EJB 3.0, all container services can be configured and delivered to any POJO in the application via annotations. In most cases, special component classes are not needed.

Let's check out how annotations are used in EJB 3.0 POJO applications via the JBoss EJB 3.0 TrailBlazer.

Develop loosely coupled service objects

One of the most important benefits of enterprise middleware, such as Java EE, is that it allows developers to build applications with loosely coupled business components. The components are only coupled via their published business interfaces. Hence, the component implementation classes can be changed without affecting the rest of the application. That makes the application more robust, easier to test, and more portable. EJB 3.0 makes it easy to build loosely coupled business components in POJO.

Session beans

In EJB 3.0 applications, loosely coupled service components are typically implemented as session beans. A session bean must have an interface (i.e., the business interface), through which other application components can access its services. The code that follows provides the business interface for our example investment calculator service. It has only one method to calculate total investment return given the start and end age of the investor, the fund growth rate, and the monthly saving amount.

 public interface Calculator {

public double calculate (int start, int end, double growthrate, double saving);

}


The session bean class simply implements the business interface. You must tell the EJB 3.0 container that this POJO class is a session bean by annotating it with the Stateless or Stateful annotations. A stateful session bean maintains the client state across several different service requests. On the contrary, stateless session bean requests are served by a random bean instance each time. Their behaviors match those of the old EJB 2.1 stateful and stateless session beans. The EJB 3.0 container figures out when to instantiate the bean object and makes it available via the business interface. Below is the code for the session bean implementation class:

 @Stateless
public class CalculatorBean implements Calculator {

public double calculate (int start, int end, double growthrate, double saving) { double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1); return saving * 12. * (tmp - 1) / growthrate; }

}


You can also specify multiple interfaces for a session bean—one for local clients and one for remote clients. Just use the @Local and @Remote annotations to differentiate the interfaces. The following snippet shows that the CalculatorBean session bean implements both a local and a remote interface. If you do not have the @Local and @Remote annotations, the session bean interface defaults to a local interface.

 @Stateless
@Local ({Calculator.class})
@Remote ({RemoteCalculator.class})
public class CalculatorBean implements Calculator, RemoteCalculator {

public double calculate (int start, int end, double growthrate, double saving) { double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1); return saving * 12. * (tmp - 1) / growthrate; }

public String getServerInfo () { return "This is the JBoss EJB 3.0 TrailBlazer"; } }


The session bean client obtains a stub object of the bean via JNDI (Java Naming and Directory Interface). Provided by the container, the stub object implements the session bean's business interface. All calls made to the stub object are routed to the container and invoked against the managed bean instances. For stateless session beans, you can obtain a new stub every time you make a call. For stateful session beans, you must cache the stub on the client side so the container knows to provide you with the same bean instance for each subsequent call. The following snippet shows how to make a call to the session bean. Later in the article, you will learn a simpler way to obtain a bean stub object.

 InitialContext ctx = new InitialContext();
cal = (Calculator) ctx.lookup(Calculator.class.getName());

double res = cal.calculate(start, end, growthrate, saving);


Session bean lifecycle management

To achieve loose coupling, the application defers the creation, pooling, and destruction of the session bean instance to the EJB 3.0 container (i.e., the Inversion of Control design pattern). And the application works only with the business interfaces.

But what if the application needs finer control over the session object? For instance, the application might need to perform database initialization when the container creates the session bean, or close external connections when the bean is destroyed. You can do these by implementing lifecycle callback methods in the bean class. These methods are called by the container at various stages of the bean's lifecycle (e.g., bean creation and destruction). In EJB 3.0, you can specify any bean method as a callback by annotating it with the following annotations. Unlike EJB 2.1, where all callback methods must be implemented even if they are empty, EJB 3.0 beans can have any number of callback methods with any method name.

  • @PostConstruct: The container immediately calls the annotated method after a bean instance is instantiated. This annotation applies to both stateless and stateful session beans.
  • @PreDestroy: The annotated method is called before the container destroys an unused or expired bean instance from its object pool. This annotation is applicable to both stateless and stateful session beans.
  • @PrePassivate: If a stateful session bean instance is idle for too long, the container might passivate it and store its state to a cache. The method tagged by this annotation is called before the container passivates the bean instance. This annotation applies to stateful session beans.
  • @PostActivate: When the client uses the passivated stateful session bean again, a new instance is created and the bean state is restored. The method tagged by this annotation is called when the activated bean instance is ready. This annotation is only applicable to stateful session beans.
  • @Init: This annotation designates initialization methods for a stateful session bean. It differs from the @PostConstruct annotation in that multiple methods can be tagged with @Init in a stateful session bean. However, each bean instance can have only one @Init method invoked. The EJB 3.0 container determines which @Init method to invoke depending on how the bean is created (see the EJB 3.0 specification for details). The @PostConstruct method is called after the @Init method.

Another useful annotation for a lifecycle method, especially for stateful session beans, is @Remove. When the application calls the @Remove tagged method through the stub, the container knows to remove the bean instance from the object pool after the method executes. Here is an example of those lifecycle method annotations in CalculatorBean:

 @Stateful
public class CalculatorBean implements Calculator, Serializable {

// ... ... @PostConstruct public void initialize () { // Initializes the history records and load // necessary data from database etc. } @PreDestroy public void exit () { // Save history records into database if necessary. } @Remove public void stopSession () { // Call to this method signals the container // to remove this bean instance and terminates // the session. The method body can be empty. } // ... ... }


Message-driven beans

The session bean services are provided over synchronous method invocations. Another important type of loosely coupled service is an asynchronous service triggered by incoming messages (e.g., email or Java Message Service messages). The EJB 3.0 message-driven beans (MDBs) are components designed to handle message-based service requests.

An MDB class must implement the MessageListener interface. When the container detects a message for this bean, it invokes the onMessage() method and passes the incoming message as the call parameter. The MDB decides what to do with the message in the onMessage() method. You can use annotations to configure which message queues this MDB monitors. When the MDB is deployed, the container uses the configuration information specified in the annotations. In the following example, the CalculatorBean MDB is invoked when the container detects an incoming message in the queue/mdb JMS queue. The MDB parses the message and performs the investment calculation based on the message content.

 @MessageDriven(activateConfig =
{
  @ActivationConfigProperty(propertyName="destinationType",
    propertyValue="javax.jms.Queue"),
  @ActivationConfigProperty(propertyName="destination",
    propertyValue="queue/mdb")
})
public class CalculatorBean implements MessageListener {

public void onMessage (Message msg) { try { TextMessage tmsg = (TextMessage) msg; Timestamp sent = new Timestamp(tmsg.getLongProperty("sent")); StringTokenizer st = new StringTokenizer(tmsg.getText(), ",");

int start = Integer.parseInt(st.nextToken()); int end = Integer.parseInt(st.nextToken()); double growthrate = Double.parseDouble(st.nextToken()); double saving = Double.parseDouble(st.nextToken());

double result = calculate (start, end, growthrate, saving); RecordManager.addRecord (sent, result);

} catch (Exception e) { e.printStackTrace (); } }

// ... ... }


Message-driven beans and POJOs
It is important to note that EJB 3.0 MDBs are not pure POJOs. They are lightweight components as they still need to implement the MessageListener interface. JBoss has a message-driven POJO solution (see References) that uses EJB 3.0-style annotations to build completely POJO-based messaging applications.


Dependency injection

In the previous section, you learned how to develop loosely coupled service components. However, to access those service objects, you need to look up stub objects (for session beans) or message queues (for MDBs) from the server's JNDI. JNDI lookup is a key step that decouples the client from the actual implementation of the service object. However, plain JNDI lookup based on string names is not elegant. Here are a few reasons:

  • The client and service must agree on the string-based name. It is a contract not enforced by the compiler or any deployment-time checks.
  • The type of the retrieved service object is not checked at compile-time and could result in a casting error at runtime.
  • The verbose lookup code with its own try-catch block is repeated and littered across the application.

EJB 3.0 features a simple and elegant way to make decoupled service objects and resources available to any POJO. Using the @EJB annotation, you can inject an EJB stub object into any POJO managed by the EJB 3.0 container. If the annotation is tagged on a field variable, the container will assign the correct value to the variable before the first time it is accessed. The following example shows how to inject a CalculatorBean stateless session bean stub into a CalculatorMDB MDB class:

 public class CalculatorMDB implements MessageListener {

@EJB Calculator cal; // Use the cal variable // ... ... }


If the annotation is tagged on a JavaBean-style setter method for a property, the container automatically calls the setter method with the correct parameters before the property is first used. The following snippet illustrates how that works:

 public class CalculatorMDB implements MessageListener {

Calculator cal; @EJB public void setCal (Calculator cal) { this.cal = cal; } // Use the cal variable // ... ... }


In addition to the @EJB annotation, EJB 3.0 supports the @Resource annotation to inject any resource from the JNDI. In the following example, I illustrate how to inject the server's default TimerService and SessionContext objects, as well as how to inject named database and JMS resources from the JNDI:

 @Resource
TimerService tms;

@Resource SessionContext ctx;

@Resource (name="DefaultDS") DataSource myDb;

@Resource (name="ConnectionFactory") QueueConnectionFactory factory;

@Resource (name="queue/A") Queue queue;


Furthermore, you can also inject a container-managed persistence manager (i.e., the EntityManager—it resembles the Hibernate Session object) into EJB 3.0 POJOs. I will cover EntityManager in this article's second installment.

Deliver container services to POJOs

In addition to managing the lifecycle and access of the loosely coupled service objects, the EJB 3.0 container also provides runtime services to its managed POJOs via simple annotations.

Transaction

The most useful container service is probably the transaction service, which ensures database integrity in case of application failure or exception. You can simply annotate a POJO method to declare its transaction attribute. The container runs the method in the appropriate transactional context. For instance, the following code declares that the container should create a new transaction to run the updateExchangeRate() method. The transaction commits when the method exits. In fact, all the methods called from within updateExchangeRate() also execute in the same transaction context, unless explicitly declared otherwise. The database operations performed in the updateExchangeRate() method either all succeed or all fail.

 @Stateless
public class CalculatorBean implements Calculator {

// ... ...

@TransactionAttribute(TransactionAttributeType.REQUIRED) public void updateExchangeRate (double newrate) throws Exception { // Update the database in a loop. // ... ... // The operations in the loop must all be successful or // the database is not updated at all. } }


Security

The container can also provide security services to authenticate users and limit access to its managed POJOs based on user roles. For each POJO class, you can specify a security domain using the @SecurityDomain annotation, which tells the container where to find the password and user role lists. The other domain in JBoss indicates that the files are users.properties and roles.properties files in the classpath. Then, for each method, you can tag a security constraint annotation to specify who is allowed to run this method. For instance, in the following example, the container authenticates all users who attempt to execute the addFund() method and only allows users with the AdminUser role to actually run it. If you are not logged in or are logged in as a nonadministrative user, a security exception will be thrown.

 @Stateless
@SecurityDomain("other")
public class CalculatorBean implements Calculator {

@RolesAllowed({"AdminUser"}) public void addFund (String name, double growthrate) { // ... ... }

@RolesAllowed({"AdminUser"}) public void addInvestor (String name, int start, int end) { // ... ... }

@PermitAll public Collection <Fund> getFunds () { // ... ... } // ... ...

@RolesAllowed({"RegularUser"}) public double calculate (int fundId, int investorId, double saving) { // ... ... } }


Generic interceptors

Both transaction and security services can be considered runtime interceptors managed by the container. The container intercepts the method calls from the EJB stub and applies transaction context or security constraints around the calls.

In EJB 3.0, you can extend the container services by writing your own interceptors. Using the @AroundInvoke annotation, you can specify any bean method as the interceptor method that will execute before and after any other bean method runs. In the following example, the log() method is the interceptor that profiles and logs the execution time of other bean methods:

 @Stateful
public class CalculatorBean implements Calculator {

// Bean methods that are to be intercepted by "log()" // ... ... @AroundInvoke public Object log (InvocationContext ctx) throws Exception {

String className = ctx.getBean().getClass().getName(); String methodName = ctx.getMethod().getName(); String target = className + "." + methodName + "()";

long start = System.currentTimeMillis(); System.out.println ("Invoking " + target); try { return ctx.proceed(); } catch(Exception e) { throw e; } finally { System.out.println("Exiting " + target);

cal.setTrace(cal.getTrace() + " " + "Exiting " + target); long time = System.currentTimeMillis() - start; System.out.println("This method takes " + time + "ms to execute"); } } }


What's next?

In Part 1 of this series, I discussed the general POJO-based programming model for EJB 3.0 and how to develop loosely coupled service objects in EJB 3.0. In Part 2, I will discuss another major aspect of EJB 3.0: managed POJO persistence. Stay tuned.

Author Bio

Dr. Michael Yuan works for JBoss. He specializes in end-to-end enterprise solutions. He is the author of three books, Nokia Smartphone Hacks, Enterprise J2ME, and Nokia Series: Developing Scalable Series 40 Applications. Yuan received a Ph.D. degree from the University of Texas at Austin.

All contents copyright 1995-2007 Java World, Inc. http://www.javaworld.com