JavaBeans:创建客户端应用
Cabin实体和 TravelAgent EJB已经部署完毕,我们打算从远程客户端对其进行访问。本节中,我们会创建一个远程客户端,连向EJB服务器,为TravelAgent EJB定位EJB远程接口,并与TravelAgent EJB进行交互,以创建Cabin实体并将其从数据库中取出。下列代码展示了一个Java应用程序,该程序新建了一个Cabin实体,设置其name、 deckLevel、shipId和bedCount成员属性,然后再用主键对其进行定位。
package com.titan.clients;
import com.titan.travelagent.TravelAgentRemote;
import com.titan.domain.Cabin;
import javax.naming.InitialContext;
import ntext;
import javax.naming.NamingException;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;
public class Client {
public static void main(String [] args) {
try {
Context jndiContext = getInitialContext( );
Object ref = jndiContext.lookup( "TravelAgentBean/remote");
TravelAgentRemote dao = (TravelAgentRemote)
PortableRemoteObject.narrow(ref,TravelAgentRemote.class);
Cabin cabin_1 = new Cabin( );
cabin_1.setId(1);
cabin_1.setName( "Master Suite");
cabin_1.setDeckLevel(1);
cabin_1.setShipId(1);
cabin_1.setBedCount(3);
dao.createCabin(cabin_1);
Cabin cabin_2 = dao.findCabin(1);
System.out.println(cabin_2.getName( ));
System.out.println(cabin_2.getDeckLevel( ));
System.out.println(cabin_2.getShipId( ));
System.out.println(cabin_2.getBedCount( ));
} catch (javax.naming.NamingException ne){ne.printStackTrace( );}
}
public static Context getInitialContext( )
throws javax.naming.NamingException {
Properties p = new Properties( );
// ... 指定厂商专有的JNDI属性
return new javax.naming.InitialContext(p);
}
}
为了访问enterprise bean,客户端首先使用JNDI获得一个连向bean所在容器的目录。JNDI是一组独立于实现的API,用于目录和命名系统。每家EJB厂商都必须提供一个与JNDI兼容的目录服务。这意味着他们必须给出一个JNDI service provider(JNDI服务提供程序),即一段类似JDBC驱动的软件代码。不同的service provider与不同的目录服务相连接,就如同JDBC一样,不同的驱动程序与不同的关系数据库相连接。getInitialContext方法使用JNDI来获得一个指向EJB服务器的网络连接。
用于获取JNDI上下文的代码和你使用哪一家EJB厂商的产品有关。如何获取与你所用的产品相配的JNDI上下文,请参考厂商文档。例如,在WebSphere中用于获取JNDI上下文的代码可能类似如下。
public static Context getInitialContext( )
throws javax.naming.NamingException {
java.util.Properties properties = new java.util.Properties( );
properties.put(ntext.PROVIDER_URL, "iiop:///");
properties.put(ntext.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejs.InitialContextFactory");
return new InitialContext(properties);
}
而针对JBoss编写的同一方法会有所不同。
public static Context getInitialContext( )
throws javax.naming.NamingException {
Properties p = new Properties( );
p.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
p.put(Context.URL_PKG_PREFIXES,
" org.jboss.naming:org.jnp.interfaces");
p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
return new javax.naming.InitialContext(p);
}
一旦建立起JNDI连接,并且通过getInitialContext方法获得了上下文,我们就可以利用上下文来查找TravelAgent EJB的远程接口了。
Object ref = jndiContext.lookup("TravelAgentBean/remote");
在本书中,我们将始终为远程客户端应用程序使用形如“TravelAgentBean/remote”这样的查找名称。你所用的实际查找名称或许有所不同,这依赖于厂商的要求。你需要把查找名称绑定到EJB服务器的命名服务上,而有些厂商可能会要求一个特殊的目录路径,或者提供一个默认的绑定。
如果使用标准的Java EE组件(Servlet、JSP、EJB或Java EE应用客户端),无论你使用哪家EJB厂商的产品,在创建JNDI InitialContext时都不需要显式的设置属性。这是因为JNDI属性可以在部署期间配置并被自动应用。一个Java EE组件会以如下方式获得其InitialContext。
public static Context getInitialContext( )
throws javax.naming.NamingException {
return new javax.naming.InitialContext( );
}
相比于为简单Java客户端手工配置JNDI属性,这种方式更为简单,也更易于移植。所有的Java EE组件都使用相同的JNDI命名系统,enterprise bean以此来查找任何服务。特别要指明的是,这些组件要求指向EJB的引用要与“java:comp/env/ejb/”名字空间绑定。例如,对于像 servlet这样一个不同的Java EE组件而言,为了查找TravelAgent EJB,下面是我们所要做的全部工作。
Object ref = jndiContext.lookup("java:comp/env/ejb/TravelAgentRemote");
在部署期间,你要使用厂商的部署工具将JNDI名称映射到TravelAgent EJB的远程接口。在后续章节里,我们会看到使用特殊的注解可以将指向EJB的引用直接注入到bean class中。我们已经看到过这种方式的一个例子,即:将EntityManager服务注入到Travel- AgentBean类里。在本书中,Java客户端应用程序需要使用显式的参数来进行JNDI查找。作为替代方案,你也可以使用一种特殊的Java EE组件,叫做Java EE应用客户端(Java EE Application Client),但是这类组件超出了本书的讨论范围。有关Java EE应用客户端组件的更多信息可以参考Java EE 5的规范。
客户端应用程序使用PortableRemoteObject.narrow方法将Object ref窄化(narrow)成一个TravelAgentRemote引用。
Object ref = jndiContext.lookup("TravelAgentRemote");
CabinHomeRemote home = (TravelAgentRemote)
PortableRemoteObject.narrow(ref,TravelAgentRemote.class);
PortableRemoteObject.narrow方法在EJB 1.1中被首次引入,并继续沿用于EJB 3.0的远程客户端。这需要支持基于IIOP之上的RMI。由于CORBA要支持许多不同的语言,而转型并非CORBA的固有功能(一些语言没有转型概念)。因此,为了获得一个指向TravelAgentRemote的远程引用,我们必须显示地对从lookup返回的对象进行窄化。
用于查找TravelAgent EJB远程接口的名称,可以是特定于厂商的默认值,特定于厂商的注解,或部署描述文件。或者,如果EJB产品中带有部署向导,还可以由部署人员使用向导来设置。JNDI的名称完全取决于部署bean的人;可以与在XML部署描述文件中设定的bean名字相同,也可以截然不同。