6.5. Using Enterprise JavaBeansSo far, we've seen the high-level details on implementing and deploying an EJB. Now let's look at how you use an enterprise bean as a client. It's important to note that a "client" for a given EJB can be many things. In a fairly typical scenario, a client may be a Java servlet or JSP, running in the same or different J2EE server as the EJBs being used. A client can also be a standalone GUI client (in J2EE parlance, an "application client") that directly connects to a local or remote EJB container and makes requests of beans. A client can also be another EJB, invoking an EJB on the same or different EJB server in order to satisfy a client request. Regardless of which of these scenarios is the case, the following details apply. 6.5.1. Local Versus Remote ClientsA local client (i.e., a client that resides in the same Java virtual machine as the EJB container) can interact with EJBs using either the local or remote interfaces for the EJB. If the EJB has a local home and client interface, this provides an efficient way for the client to make requests, since their method calls don't need to be marshaled into a remote method call and transmitted over an RMI or IIOP connection to the EJB container. If the EJB doesn't have a local client and home interface, or if the local interfaces don't expose the functionality that the client needs (remember that local and remote interfaces for the same EJB don't need to be equivalent to each other), a local client must use its remote home and client interface. Local EJB interfaces were introduced in EJB 2.0, so if you are using EJB 1.1, your EJBs can have only remote interfaces. Remote clients (i.e., clients that run in a different Java virtual machine than the EJB container) must use the EJB's remote client and home interfaces . Remote interfaces are RMI client stubs (see Chapter 13 for full details on Java RMI), and the underlying remote method protocol being used can be RMI/JRMP (the "native" RMI protocol) or RMI/IIOP (the CORBA protocol). In order for a client to maintain portability across different EJB implementations, it's important that remote references obtained from the EJB container be cast to their interface types using the narrow( ) method on javax.rmi.PortableRemoteObject. This ensures that RMI/IIOP remote references will be safely cast to their expected type. 6.5.2. Finding Home Interfaces Through JNDIOnce an enterprise bean is deployed within an EJB container, the home interface(s) for the bean have been exported under a particular name using JNDI according to the settings in the deployment descriptor. As a client, you need to know how to connect to the JNDI context of the EJB/J2EE server and know the name for the bean home interface you're interested in. Full details on using JNDI and the options for creating InitialContexts can be found in Chapter 9, but in general you need to set connection properties required for the JNDI provider you're connecting to and then create an InitialContext object that points to the root of the naming system: Hashtable props = new Hashtable( ); // Specify the necessary properties ... // Try looking up the context javax.naming.Context ctx = null; try { ctx = new javax.naming.InitialContext(props); } catch (NamingException ne) { System.out.println("Failed to connect to JNDI service for EJB server"); } If you're running within a J2EE environment (e.g., the EJB client is actually a web component running within the same J2EE server), you typically won't need to specify any connection properties. The J2EE server is configured so that the default JNDI connection properties point to its internal JNDI service, and you can use the no-argument form of the InitialContext constructor: ctx = new javax.naming.InitialContext( ); Now that we have a JNDI naming context from the EJB server, we can look up the home interface for the bean we're interested in. Assuming that we deployed our bean and specified to the EJB container that its JNDI name should be ProfileManagerHome, we can obtain a reference to a home interface to the bean with code such as the following: ProfileManagerHome pHome = null; try { Object pRef = ctx.lookup("ProfileManagerHome"); pHome = (ProfileManagerHome)PortableRemoteObject.narrow(pRef, ProfileManagerHome.class); } catch (NamingException ne) { System.out.println("Failed to lookup home for ProfileManager bean."); } Notice that we're using the javax.rmi.PortableRemoteObject.narrow( ) method to safely cast the remote reference obtained from the EJB container to the expected home interface type. This allows our client to work safely with EJB servers that provide RMI/IIOP remote references to their beans. 6.5.3. Creating and Finding BeansAs we saw in the previous sections, home interfaces for an EJB contain methods that allow a client to create new beans or find existing beans (for entity beans). Continuing our example client, assuming we're using our ProfileManager bean and its corresponding home interface shown in Example 6-3, we can create a reference to a ProfileManager bean as follows: ProfileManager pServer = null; try { pServer = pHome.create( ); } catch (RemoteException re) { System.out.println("Remote exception while creating ProfileManager: " + re.getMessage( )); } catch (CreateException ce) { System.out.println("Error occurred during creation: " + ce.getMessage( )); } We're using the create( ) method defined on our ProfileManagerHome interface. Since this is a remote home interface, we need to catch both RemoteException and CreateException in our client code, in case some error occurs while communicating with the remote EJB container or during the create process itself. Assuming all goes well, we can now use our ProfileManager bean, invoking its business methods as needed by our client. In the sections that follow, we'll examine client usage of each type of EJB (session, entity, and message-driven) in more detail, after we discuss the implementation and deployment details of each EJB component type. 6.5.4. EJB Handles and Home HandlesEvery bean's remote client interface (if it has one) extends the EJBObject interface. Among other things, this interface allows the client to obtain a serializable handle on the remote enterprise bean. This handle is a persistent reference to the bean that can be serialized and then stored in local storage on the client side or emailed as an attachment to other users, for example. Later, a client can deserialize the handle object and continue interacting with the bean it references. The handle contains all of the information needed to reestablish a remote reference to the enterprise bean it represents. Since this is useful only for beans that are still valid when the handle is reconstituted, it is usually applicable only to entity beans, since they are persistent. Handles to session beans can also be used this way, as long as the handle is serialized and deserialized during the lifetime of the session bean (e.g., the EJB container doesn't shut down in the meantime or the session bean is not removed from memory by the container). You can get the handle for a bean using the getHandle( ) method on a remote bean object reference: ProfileManager profileServ = ...; Handle pHandle = pServ.getHandle( ); The getHandle( ) method returns a javax.ejb.Handle object. Typically, the Handle implementation for a particular EJB is provided by the EJB container and is generated automatically from the EJB remote client interface and bean implementation class that you provide. Every Handle implementation is a Serializable object, which allows it to be transmitted to remote clients and to be stored in serialized format, if needed: ObjectOutputStream oout = ...; oout.writeObject(pHandle); Later, you can read the object back from its serialized state and obtain a reference to the same remote EJB, using the getEJBObject( ) method on the handle: ObjectInputStream oin = ...; Handle pHandleIn = (Handle)oin.readObject( ); EJBObject pRef = pHandleIn.getEJBObject( ); ProfileManager pIn = (ProfileManager)PortableRemoteObject.narrow(pRef, ProfileManager.class); ProfileBean prof = pIn.getProfile("JohnSmith"); When we call getEJBObject( ) on the Handle, the Handle communicates with the source EJB container and attempts to construct a remote reference to the bean that the handle came from originally. As with other situations in which we receive a remote reference from the EJB container, we use the PortableRemoteObject.narrow( ) method to safely cast the remote EJB interface to the expected type, and then we can use the EJB. It's also possible to get a handle for a remote home EJB interface. The same basic sequence of events can be followed for obtaining and serializing a home handle: obtain the handle by calling the getHomeHandle( ) method on a home stub instance, use the appropriate serialization technique (instantiate an ObjectOutputStream, store the handle in a binary field in a database, and so on) to store the handle: ProfileManagerHome pHome = ... HomeHandle pHomeHandle = pHome.getHomeHandle( ); ObjectOutputStream oout = ...; oout.writeObject(pHomeHandle); We can reconstitute the serialized HomeHandle later and use its getEJBHome( ) method to get a new remote home reference for the same EJB: ObjectInputStream oin = ...; HomeHandle pHomeHandleIn = (HomeHandle)oin.readObject( ); EJBHome pHomeRef = pHomeHandleIn.getEJBHome( ); ProfileManagerHome profileHomeIn = (ProfileManagerHome)PortableRemoteObject.narrow( pHomeRef, ProfileHome.class); ProfileManager pServer = profileHomeIn.create( ); The EJB specification doesn't dictate the usable lifetime of a home handle. It may become invalid after the source EJB container restarts, it may expire after some timeout period, or it may remain valid for an extended period of time. Consult your EJB vendor documentation for specific details. |