Development Guidelines

Earlier, we looked at the various types of EJBs supported by WebLogic, and the different circumstances under which you should use them. In this section, we examine some of the important design principles you should adopt when building an enterprise application using EJBs. Careful choices can help you extract the best performance out of your EJBs in a WebLogic environment.

10.2.1 Collocation

The EJB 2.0 standard introduces the notion of local EJBs, which provide lightweight access from other EJBs and local clients running within the same server. An EJB, whose home interface extends EJBLocalHome and whose component interface extends EJBLocalObject, is accessible only to local clients running within the same JVM. Unlike remote EJBs where the client communicates with the EJB over RMI-IIOP, a client can communicate with a local EJB without incurring the overheads of marshalling and unmarshalling associated with remote method calls. Because both the client and the EJB reside within the same JVM, the EJB container is able to use pass-by-reference semantics, similar to an ordinary Java method call.

Consider using local EJBs when you want to ensure they are accessible only to other server-side components or local clients running within the same JVM. This means your local EJB interfaces will not be exposed to other remote clients i.e., any client running on another JVM, or even another machine. EJBs that participate in container-managed relationships (CMR) with other EJBs are required to support local EJB interfaces. They may still expose a remote interface if they need to be accessible to remote clients. We will cover WebLogic's support for CMP and CMR in Chapter 11.

Clearly, a client running outside WebLogic Server cannot avoid the cost of marshalling and unmarshalling associated with method calls to a remote EJB. But you can avoid these overheads when the client and the remote EJB are collocated within the same JVM. The enable-call-by-reference element in the weblogic-ejb-jar.xml descriptor file lets you enable this optimization if the client and the EJBs run within the same VM. It indicates whether the EJB container should pass arguments by reference when a local client invokes a method on an EJB, instead of using traditional RMI "pass-by-value" semantics. When enabled, WebLogic is able to achieve better performance by using "pass-by-reference" semantics automatically, provided both the client and EJB are deployed as part of the same EAR:

RegistrationEJB ... True ...

The default value of this setting is False.[2] This means that if a servlet invokes a method on a collocated EJB, WebLogic automatically will use pass-by-reference semantics for the method call (assuming the web application and the EJBs are deployed as part of the same enterprise application). You need to explicitly disable this setting if you do not want to use pass-by-reference semantics for local clients. In that case, arguments to EJB methods are always passed by value, regardless of whether the client is local or remote.

[2] In WebLogic 7.0, the default value for the enable-call-by-reference element is True, which is not strictly compliant.

10.2.2 Network Latency

An enterprise application built on an n-tier architecture usually will involve an EJB tier i.e., a layer of reusable business objects that mediates access to the underlying database. Typically, the EJB tier will manifest itself as a cluster of servers that host your EJB components. Unfortunately, this also introduces problems of network latency between the web tier (presentation), the EJB tiers (application), and the data-access tier (data). Network traffic between the web server and the EJB tier also contributes to the latency between the browser and the web server. The performance of your application depends on your ability to minimize the amount of network traffic between the web server, the EJB tier, and the underlying database.

At some point during development or performance testing, it is crucial that you monitor the strain on your network resources under high load. Let's examine some of the ways in which you can optimize use of network resources, especially when you've used EJBs to encapsulate your business logic.

A typical business process involves updates to multiple entity beans. If these updates occur directly from a JSP or a servlet, a number of method calls to remote EJBs means that you incur a performance cost because of the increased network traffic. Instead, you should use session beans to wrap access to your entity beans. By modeling your business processes using session beans in this way, you can limit the amount of network traffic between the client and remote EJBs. For instance, if a student needs to register for multiple university courses, you could create a Registration session bean that wraps updates to the Student and Course entity EJBs. A single remote call to the session bean now replaces multiple remote calls to all the entity beans involved. The session bean acts as a façade and hides access to the actual entity beans. In this scenario, where the remote clients always use the session beans to access the entity beans, you even could implement all the entity beans as local EJBs because only the session beans need to be accessible to remote clients.

On other occasions, you could perform JDBC access directly from within a session bean. For instance, if a JSP needs to render an HTML form that displays a list of all offered courses, you could implement an EJB method that returns a disconnected JDBC rowset with the necessary data. This approach limits the duration of the transaction and minimizes network traffic. Calling a find method on an entity bean that returns a list of EJB instances requires considerable network bandwidth. For each EJB reference, you need to fetch the actual data from the underlying database. This could result in unacceptable performance of your application.

Another way to restrict the network traffic is to simply limit the number of method calls to remote EJBs. For instance, bulk getter and setter methods minimize the number of round trips between the client and the underlying database. Suppose the Student EJB exposes the following remote interface:

public interface Student extends EJBObject { ... public String getHouseNo( ) throws RemoteException; public String getStreet( ) throws RemoteException; public String getCity( ) throws RemoteException; public String getPostCode( ) throws RemoteException; public void setHouseNo(String arg); throws RemoteException; public void setStreet(String arg); throws RemoteException; public void setCity(String arg); throws RemoteException; public void setPostCode(String arg); throws RemoteException; ... }

Then you easily could simplify the EJB interface with remote methods that use a value object AddressVO instead:

public interface Student extends EJBObject { ... public AddressVO getAddress( ) throws RemoteException; public void setAddress(AddressVO vo) throws RemoteException; ... }

A value object exists solely for the purpose of transporting data between the client and the remote EJB. Typically, it is implemented as an immutable, serializable Java object with getter methods for each attribute. Current literature is moving toward calling them transfer objects.

10.2.3 Using EJB Homes

In order to obtain a reference to the EJB instance, you need to first look up the name of the EJB's home in the JNDI tree. You should reuse the results of the first lookup and avoid subsequent JNDI lookups:

private EJBHome regHome; ... public RegistrationHome getRegistrationHome( ) throws NamingExeption { if (regHome == null) { InitialContext ctx = new InitialContext( ); Object home = ctx.lookup("org.foo.bar.RegistrationHome") regHome = (EJBHome) PortableRemoteObject.narrow(home, org.foo.bar.RegistrationHome.class); } return (RegistrationHome) regHome; }

A better approach would be to build a cache of EJB home objects, and then use the cached values the next time you need to access the EJB home objects. For an external client, the only change is how you obtain a reference to the initial JNDI context:

try { Hashtable env = new Hashtable( ); env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); env.put(Context.PROVIDER_URL, "t3://server:port"); //URL of a running WebLogic instance InitialContext ctx = new InitialContext(env); Object home = ctx.lookup("org.foo.bar.RegistrationHome"); RegistrationHome regHome = (RegistrationHome) PortableRemoteObject.narrow(o, org.foo.bar.RegistrationHome.class); //now create an instance of a session bean } catch (NamingException ene) { //handle naming exceptions }

Note that because local EJBs do not follow RMI semantics, you do not need to use the PortableRemoteObject.narrow( ) method. For instance, if the Registration bean is designed as a local EJB, you simply need to cast the EJB home object to the desired type:

Object home = ctx.lookup("org.foo.bar.RegistrationHome"); RegistrationHome regHome = (RegistrationHome) home;

You also can invoke the getHomeHandle( ) method on an EJB instance to obtain a handle to the EJB's home object. This handle is an abstract network reference to the EJB's home object, which can be passed to another client easily. The client can then invoke the getEJBHome( ) method on the handle to access the EJB's home object. You also can serialize the home handle to a file for later use.

By default, the home handle encapsulates the IP address of the server from which the EJB home object was retrieved. This may result in unexpected behavior if your network configuration relies on firewall security. An EJB home handle that is sent through a firewall may not be able to locate the home object. You can fix this problem by enabling reverse DNS lookups on the target server. In that case, WebLogic stores the DNS name of the server in the EJB home handle instead of its IP address. You can enable reverse DNS lookups for a chosen server from the Configuration/Tuning tab in the Administration Console.[3]

[3] In WebLogic 7.0, use the Configuration/Network tab to enable reverse DNS lookups.

10.2.4 Conserving Transactions

Distributed transactions have a significant bearing on WebLogic's performance. You need to closely monitor the duration of your transactions at runtime because they impact the performance and scalability of your application. Typically, EJBs dip into a pool of JDBC connections to service their database requests. A connection that participates in a distributed transaction is in fact engaged for the duration of the transaction. So, short-lived transactions improve the efficiency of the connection pool because it can then serve more connection requests at a time. Short-lived transactions also limit the duration for which the transaction needs to hold on to any database locks on the underlying data. Thus, we find that the duration of distributed transactions affects both the efficiency of the connection pool and the database resources needed by your application.

WebLogic also lets you specify the transaction age for EJBs that support container-managed transactions. The trans-timeout-seconds element in the weblogic-ejb-jar.xml descriptor file determines the maximum time allowed for a transaction to complete:

RegistrationEJB ... 120 ...

Alternatively, if the EJB supports bean-managed transactions, you can specify the maximum age for the transaction using the setTransactionTimeout( ) method on a UserTransaction object.

Whenever possible, we recommend that you rely on WebLogic's support for container-managed transactions. Container-managed transactions provide a standard, flexible way for using EJBs in a distributed transaction. When you let the EJB container manage the transaction boundaries, your EJB code then can focus on the application logic instead of ensuring that transactions are handled correctly. In addition, the new CMP 2.0 framework requires automatic container support for transactional access to entity EJBs and their related EJB instances. You also should remember that WebLogic demarcates transactions on a per-method basis. Container-managed transactions should be sufficient for your needs, unless you need to support multiple transactions within an EJB method, or the EJB method requires complex logic where transactions are committed under certain specific conditions.

You also should be careful about how you set the transaction attributes for the EJB methods. The following XML fragment from a typical ejb-jar.xml descriptor file may look very innocuous:

RegistrationEJB ... Container ... RegistrationEJB * Required

However, ask yourself whether you really need a transaction context for every EJB method. If the answer is no, you should modify the deployment descriptor and explicitly specify which methods require a transaction context when they are invoked by the EJB container. By default, all EJB methods can participate in a transaction context, if it exists.

Be careful when you explicitly specify a NotSupported setting for EJB methods that will not participate in a transaction context. It could actually impact the performance of your application. When a client invokes such an EJB method within a transaction context, WebLogic needs to temporarily suspend the existing transaction, then execute the EJB method, and finally resume the earlier transaction after the method completes execution. A transaction setting of Supports for an EJB method is preferable because it is easier for WebLogic to invoke the method as part of a transaction context, if it exists.

If the EJB supports container-managed transactions, make sure that you use an XA-aware data source.

Clients participating in transactions should not hold on to valuable resources such as database connections, cursors, and locks for any longer than is required, and should always endeavor to complete a transaction before exiting.

10.2.5 Message-Driven Beans

An MDB is a standard JMS consumer that responds to messages delivered to a JMS destination. MDBs are invoked asynchronously when a JMS message arrives on a particular queue or topic. WebLogic creates a pool of MDB instances that can handle large volumes of messages sent to a JMS destination. Upon receiving a JMS message, WebLogic automatically picks up an EJB instance from the pool and invokes the onMessage( ) method on the instance. The MDB instance is returned to the pool once the JMS message has been processed. An MDB can be implemented without tying it to the actual JMS destination. It is only during deployment time that you configure the topic/queue that the MDB must listen to.

An MDB can handle only JMS messages received on a single topic or queue. If you need to handle multiple JMS destinations, you need to develop separate MDBs, one for each destination. Alternatively, you could build an ordinary Java client that consumes messages from the multiple destinations.

A client interacts with an MDB indirectly by sending a JMS message to its configured topic/queue. Because a client cannot invoke an MDB directly, it doesn't declare a home or any component interfaces. Furthermore, the only transaction settings that are allowed for MDBs are Required and NotSupported because no client can ever pass a transaction context to an MDB.

The destination-type element in the ejb-jar.xml descriptor file indicates whether the MDB is listening to a queue or a topic. The destination-jndi-name element in the weblogic-ejb-jar.xml descriptor associates the MDB with the actual JNDI name to which the JMS queue or topic has been bound. The following portions from the deployment descriptors show how to bind an MDB to a particular JMS queue:

EventsQEJB org.foo.bar.EventsQHandlerBean Container javax.jms.Queue jdbc/myds javax.sql.DataSource EventsQueueHandlerEJB 15 5 org.foo.bar.MyQueue jdbc/myds myds

In addition, you can use the pool element to specify the initial and maximum size for the pool of MDB instances. The onMessage( ) method provides code for handling the JMS message. Whenever a message arrives on a JMS destination, WebLogic taps into the EJB pool and invokes the method automatically. The EJB instance is returned to the pool once the JMS message has been processed.

If an MDB supports container-managed transactions (i.e., the transaction attribute has been set to Required), the EJB container includes the receipt of the JMS message as part of the EJB's transaction. Otherwise, the receipt of the JMS message is always outside the scope of the transaction. Additionally, JMS messages are automatically acknowledged when the transaction commits. Otherwise, the message acknowledgment occurs outside the scope of the EJB's transaction. In this case, the EJB container uses the acknowledge-mode element in the ejb-jar.xml descriptor file to determine the acknowledgment semantics.

Chapter 8 is dedicated to how you can configure WebLogic JMS.

10.2.5.1 Using foreign JMS providers

MDBs also can be configured to work with foreign JMS providers. MDBs that utilize container-managed transactions can support Exactly-once semantics with foreign JMS providers. Furthermore, WebLogic will enlist the foreign JMS resource in a distributed transaction automatically. Of course, you need to ensure that both the foreign JMS provider and the JMS connection factory configured for it are XA-aware. The following portion from the weblogic-ejb-jar.xml descriptor file shows how to register an MDB with a queue hosted by an IBM MQSeries server:

SomeMDB com.sun.jndi.fscontext.RefFSContextFactory file:/MQJNDI/ mq.MyCF mq.MyQueue 30

As you can see, we've specified the initial context factory and the provider URL needed to connect to the foreign JMS provider. In addition, we've specified the JNDI names of the JMS connection factory and queue that reside on IBM MQSeries. The jms-polling-interval-seconds element specifies the time interval between attempts by the EJB container to reconnect to the foreign JMS server, in case the JMS destination it hosts becomes unavailable.

Категории