常规访问方式
编写过EJB(Enterprise Java Bean)访问程序的朋友都知道,通过客户端或服务端的程序访问EJB(即获得一个EJB Remote或Local对象)通常要经历以下几个步骤:
(1)创建一个初始化上下文(initial context);
(2)通过JNDI查找对应的EJB上下文对象;
(3)通过获得的上下文对象获得一个Home或者LocalHome对象(获得Home对象通过PortableRemoteObject对象的narrow方法,获得LocalHome对象通过强制类型转换);
(4)最后通过Home或LocalHome的create方法创建一个Remote或Local对象,通过Remote或Local对象真正使用服务器的EJB提供的方法。
代理类设计
如果我们能够把以上4个步骤封装在一个类(本文叫EJBAgent)的方法(getRemote和getLocal)内就可以使得获得一个Remote或Local对象只需要提供一个JNDI的名称,避免了重复拷贝以上4个步骤的代码。写过EJB访问代码的程序员都知道,1~3步是很容易封装起来的,以下是典型的代码:
/*1.*/ InitialContext context = new InitialContext (properties);
/*2.*/ Object home = context.lookup(JNDIName);
/*3.*/ EJBHome ejbHome =
(EJBHome) PortableRemoteObject.narrow (home, EJBHome.class);
第一步获得一个Context,主要是获得应用服务器的环境参数,我们可以把存放环境变量的pertieserties对象作为参数,第二步是查找EJB上下文对象,可以把JNDIName作为参数,第三步获得Home对象,我们可以统一获得Home对象的父类:EJBHome,应用程序在具体根据实际使用的Home接口作类型转换。
第四步通过create方法创建一个Remote或Local对象封装就有一定困难了,因为我们在第三步获得的是一个EJBHome的接口,而我们知道,EJBHome接口本身不提供create方法(对EntityBean有可能是findByPrimaryKey的方法),而是由具体的应用程序的Home接口定义的,所以如果我们直接在代码体写ejbHome.create(),会产生一个编译错误,提示方法create没找到。要使程序能够执行create方法,一种办法是把ejbHome强制类型转换成应用程序定义的EJBHome类型,即(EJBHome)换成(EJBExampleHome),其中EJBExampleHome是应用程序定义的EJBHome接口,但这样必须在代码体中写入应用程序定义的EJBHome类名称,达不到通用处理的目的。但我们知道,通过PortableRemoteObject.narrow出来的EJBHome其实是应用程序的定义的Home接口,即虽然我们使用(EJBHome)作类型转换,但ejbHome实际指向的对象是EJBExampleHome,其中就定义create方法,我们可以采用动态调用的方式调用create方法,从而避免在编译时产生错误。java.lang.reflect包提供了通过方法名称动态调用方法的Method类。以下代码是上述思想的实现:
/*3.*/ EJBHome ejbHome =
(EJBHome) PortableRemoteObject.narrow (home, EJBHome.class);
/*4.*/ Class ejbHomeClass = ejbHome.getClass();
/*5.*/ Method method = ejbHomeClass.getDeclaredMethod("create",null);
/*6.*/ Object remoteObject = method.invoke(ejbHome,null);
其中第4步是获得ejbHome的Class,可能有部分朋友以为是获得EJBHome的类,其实获得的Class是应用程序定义的EJBHome接口:应用程序定义的类EJBExampleHome。有兴趣的读者可以通过getName()方法获得实际Class名称作判断。第五步通过获得的Class获得一个定义的create方法。getDeclaredMethod有两个参数,第一个是方法的名称,是String类型,第二个是参数类型,是Class数组。由于create方法是没有参数,所以getDeclaredMethod第二个参数是null。需要注意的是getDeclaredMethod是动态执行的,所以第一参数:方法名称如果写错,编译时是不会产生错误的,在实际执行时才会报错,抛出“没有该方法”的异常:NoSuchMethodException。第6步通过动态调用create方法创建Remote对象。与getDeclaredMethod方法对应,Method的invoke方法也有两个参数,第一个参数是定义执行方法的对象,Object类型,第二个参数是调用方法的参数,是Object数组,因为create方法没有参数,所以第二个参数设置为null。
例子介绍
以下以WebLogic6.1作应用服务器为例子说明EJBAgent的使用方法。我们建立一个叫EJBExample的SessionBean,其Home接口为EJBExampleHome,Remote对象为EJBExampleRemote,其JNDI名称为EJBExample。通过以下代码体获得一个EJBExampleRemote的对象:
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL,"t3://localhost:7001";);
EJBAgent agnet = new EJBAgent(properties);
EJBExampleRemote remote = (EJBExampleRemote)agent.getRemote("EJBExample");
通过获得的remote对象就可以操作服务器端EJBExample的方法。我们可以看到,整个过程只需要两个参数:应用服务器环境properties和对应的EJB JNDI名称,强制类型转换也只需要在获得Remote对象时才使用到。而且通常一个应用程序是连接到一个应用服务器上,所以properties参数通常只需要设置一次,这样可以只产生一个EJBAgent实例供多个客户端程序使用,既减少了代码量,也提高了重用性。如果我们想获得的是一个Local对象,则可以不必构造一个properties对象作为参数,为获得Local对象的程序通常是运行在应用服务器端的SessionBean,直接使用new InitialContext()则可以获得应用服务器的环境参数。所以EJBAgent提供了有参和无参两个构造方法,分别供Client端和Application Server端的应用程序使用。具体的实现请看EJBAgnet.java。下面就EJBAgnet的部分关键代码作说明:
(2)以静态static的方式定义EJBAgent使用的context对象:避免EJB访问程序多次重新获得Context。在局域网的环境中,从clinet获得一个Context的时间在1~3秒范围内,笔者在局域网用WebLogic6.1作应用服务器作过时间比较,获得一个Context平均花1.3秒,而获得一个Home和Remote对象只分别花17和36毫秒,所以把获得的Context用静态方式缓存以供调用时使用大大减少了应用程序访问EJB的时间;
(2)针对Entity Bean的访问增加了getRemoteByKey和getLocalByKey的方法,可以通过JNDI Name和Primary Key的对象获得一个EntityBean Remote或Local对象,同样通过动态调用的方式实现。与create方法创建EJB的区别是findByPrimaryKey的方法需要有参数调用。