第二节 一个执行Bean的通用框架
上面的例子中,客户端程序是通过网络来调用的。我们首先也不要求那么的复杂,只要写一个能够本地的Session Bean的框架就可以了。
Home接口和Remote接口都是没有实现的,要调用Home接口和Remote接口的方法,一定要实现Home接口和Remote接口。但是我们真的要一个一个地去实现他们吗?显然即使能够实现,也不是好的方案。还好,Java从1.3提供的
Proxy机制可以帮助我们实现一个通用的Home接口和Remote接口。下面让我们先来了解Proxy吧。
Proxy用于动态地创建一个或多个接口的“实现“。这里所说地“实现”并不是调用Proxy的方法后,就有新的实现接口的二进制代码产生。只是用Proxy动态创建的对象(准确地说是Proxy对象)可以将用户的调用进行拦截,拦截的内容包括:方法名和用户传递的参数。
有点抽象吧,让我们以创建 CartHome接口的Proxy对象为例来帮助我们理解Proxy。为了创建一个接口的Proxy对象,先要创建一个Proxy类。
下面是创建CartHome接口的代理类的代码
Class cartHomeProxyClass = Proxy.getProxyClass( CartHome.class.getClassLoader(), new Class[]{CartHome.class} ); |
Proxy.getProxyClass()有两个参数,第一个参数为ClassLoader,表示我们创建的cartHomeProxyClass代理类由这个ClassLoader管理。第二个参数表示我们要在新创建的Proxy类“实现”哪几个接口。这里我们只实现了CartHome接口。
Proxy类创建好了以后,就可以调用它的构造函数创建一个Proxy对象,下面为创建CartHome的Proxy对象代码:
InvocationHandler handler = new CartHomeInvocationHandler(…);
CartHome cartHome = (CartHome) cartHomeProxyClass.getConstructor( new Class[] {InvocationHandler.class }). newInstance(new Object[] { handler });
|
我们用两步创建了一个Proxy对象,Proxy中提供了newProxyInstance()方法可以将上面的两步合为一步创建Proxy对象。一步创建CartHome Proxy对象的代码为:
InvocationHandler handler = new CartHomeInvocationHandler(…); CartHome cartHome = (CartHome) Proxy.newProxyInstance( CartHome.class.getClassLoader(), new class[]{CartHome.class}, handler ); |
在创建Proxy对象的时候,要一个InvocationHandler对象。上面所说的Proxy对象拦截的用户调用的内容就会传入到我们实现的InvocationHandler对象中。InvocationHandler的声明为:
public interface InvocationHandler { public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable; } |
当用户调用cartHome.create( “Xiao Min” )时,CartHomeInvocationHandler实现的invoke()方法就会被调用,参数包括:
1) proxy,就是用CartHome的Proxy类创建的Proxy对象,即cartHome对象。
2) method就是CartHome接口的create(String person)方法。
3) args就是调用create(String)方法时用户传入的参数,即“Xiao Min”
对被拦截的cartHome.create(String person)方法在InvocationHandler.invoke()中进行解释执行,返回同CartHome.create(String person)返回类型一样的对象(即Cart类型对象)。对CartHome方法调用进行拦截的InvocationHandler代码框架如下:
public class CartHomeInvocationHandler implements InvocationHandler {
//变量声明 ...
public CartHomeInvocationHandler(...) { }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { Cart cart = ...;
....; return cart; } } |
现在我们可以对调用一个接口的Proxy对象的方法进行拦截,解释,执行并返回所要求得结果类型了。
因为Cart类也是一个接口类,所以也要用Proxy创建一个Cart的Proxy类。因此,CartHomeInvocationHandler的定义可以细化为:
public class CartHomeInvocationHandler implements InvocationHandler {
public CartHomeInvocationHandler(...) { }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { InvocationHandler handler = CartInvocationHandler(...); Cart cart = (Cart)Proxy.newProxyInstance( proxy.getClass().getClassLoader(), new Class[]{Cart.class}, handler );
....; return cart; } } |
根据Sun的EJB规范,对被创建的Cart Proxy对象的方法进行调用,就是对某个CartBean对象的同名方法(指方法明,参数类型和返回类型都相同)进行调用。因此,被创建的Cart Proxy对象中用到的CartInvocationHandler类可以写为:
public class CartInvocationHandler implements InvocationHandler {
private CartBean cartBean_;
public CartInvocationHandler( CartBean cartBean ) { cartBean_ = cartBean; }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { Method m = cartBean_.getClass().getMethod( method.getName(),method.getParameterTypes() );
if( m != null ) return m.invoke( cartBean_, args ); throw new RemoteException( "Fail to find the method:" + method ); } } |
这里我们忽略了对异常的处理。我们利用Java的反射机制寻找在CartBean对象中对应的方法并对它进行调用。
如果用户每调用一个代理对象cartHome的create方法时,创建一个新的CartBean对象的话,CartHomeInvocationHandler类的定义就可以为:
public class CartHomeInvocationHandler implements InvocationHandler {
public CartHomeInvocationHandler(...) { }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { CartBean cartBean = new CartBean(); InvocationHandler handler = CartInvocationHandler(cartBean ); Cart cart = (Cart)Proxy.newProxyInstance( proxy.getClass().getClassLoader(), new Class[]{Cart.class}, handler ); … return cart; }
|
根据Sun EJB Container规范,Session Bean的Home接口中定义的create方法应该调用Bean中对应的ejbCreate()方法,CartHomeInvocationHandler可进一步细化为:
public class CartHomeInvocationHandler implements InvocationHandler {
public CartHomeInvocationHandler() { }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { CartBean cartBean = new CartBean(); InvocationHandler handler = CartInvocationHandler(cartBean ); Cart cart = (Cart)Proxy.newProxyInstance( proxy.getClass().getClassLoader(), new Class[]{Cart.class}, handler ); if( method.getName().equals( “create” ) ) { Method m = cartBean.getClass().getMethod(“ejbCreate”, method. GetParameterTypes() ); if( m == null ) throw new RemoteException(“fail to find the ejbCreate()”); m.invoke( cartBean, args ); } return cart; }
|
到现在为止我们已经完成了CartHomeInvocationHandler和CartInvocationHandler类, 可以编写测试程序来测试一下了。
InvocationHandler handler = new CartHomeInvocationHandler(); CartHome cartHome = (CartHome) Proxy.newProxyInstance( CartHome.class.getClassLoader(), new class[]{CartHome.class}, handler ); Cart cart = cartHome.create( "Xiao Min" ); cart.addBook( "Effective Java"); cart.addBook( "Master Java");
Vector books = cart.getContents(); for( int i = 0, n = books.size(); i < n; i++ ) { System.out.println( books.get( i ) ); } |
虽然,我们没有实现CartHome接口和Cart接口,但是通过运用Java的Proxy机制,我们可以执行CartBean中的方法了。
如果为每一个具体的Home接口和Remote接口都编写各自的InvocationHandler,虽然工作量不大,但是不能做到通用。能不能够为所用的Home接口和Remote接口编写通用的InvocationHandler呢?答案是肯定的。我们分析一下如何编写通用的InvocationHandler。
先考察Remote接口的通用InvocationHandler的编写。虽然我们传递到CartInvocationHandler构造函数的参数类型是CartBean,但是在CartInvocationHandler.invoke()方法中调用CartBean的方法是使用反射机制。这就是说我们只传递一个Object对象到CartInvocationHandler中,CartInvocationHandler.invoke()也可以工作。我们将CartInvocationHandler改名为RemoteInvocationHandler以表示我们编写的是一个通用的Remote接口的InvocationHandler。RemoteInvocationHandler的新代码如下:
public class RemoteInvocationHandler implements InvocationHandler {
private Object bean_;
public RemoteInvocationHandler ( Object bean ) { bean_ = bean; }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { try { Method m = bean_.getClass().getMethod( method.getName(),method.getParameterTypes() );
if( m != null ) return m.invoke( bean_, args ); throw new RemoteException( "Fail to find the method:" + method ); }catch( Exception ex ) { throw new RemoteException(“Fail to execute method:” + method, ex ); } } } |
再考察Home接口的通用InvocationHandler的编写。在CartHomeInvocationHandler中,每次调用CartHomeInvocationHandler.invoke()时,都要创建一个CartBean对象。如果我们有什么方法只要知道CartBean的名字,也能创建CartBean的话,那么我们的CartHomeInvocationHandler就可以改造成为一个通用的InvocationHandler。
Sun的EJB规范中规定,一个Bean必须要有一个不带参数的构造函数。这就是说我们在构造Bean时不能传递参数到Bean 中。如果要传递参数的话就只能通过函数调用。EJB规范中,我们在构造好Bean以后,可以立即调用ejbCreate方法传递参数到Bean中(在这一点上,Sun完全没有遵守面向对象的守则,只是用一种约定)。既然Bean中一定有一个不带参数构造函数,我们完全可以用Bean的名字构造一个Bean对象。将CartHomeInvocationHandler名字改为HomeInvocationHandler以表示我们编写的是一个通用的Home接口的InvocationHandler,其代码如下:
public class HomeInvocationHandler implements InvocationHandler { private String remoteClassName_; private String beanClassName_;
public HomeInvocationHandler( String remoteClassName, String beanClassName ) { remoteClassName_ = remoteClassName; beanClassName_ = beanClassName; }
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { try { Class beanClass = Class.forName( beanClassName_ ); Class remoteClass = Class.forname( remoteClassName_ ); Object bean = beanClass.newInstance(); InvocationHandler handler = RemoteInvocationHandler(bean ); Object proxyObj = Proxy.newProxyInstance( proxy.getClass().getClassLoader(), new Class[]{remoteClass }, handler ); if( method.getName().equals( "create" ) ) { Method m = bean.getClass().getMethod(“ejbCreate”, method. getParameterTypes() ); if( m == null ) throw new RemoteException(“fail to find the ejbCreate()”); m.invoke( bean, args ); } return proxyObj; }catch( Exception ex ) { throw new RemoteException( “fail to call the method:” + method, ex ); } } } |
使用新编写的HomeInvocationHandler和RemoteInvocationHandler,我们的测试代码就变为:
InvocationHandler handler = new HomeInvocationHandler( “Cart”, “CartBean”); CartHome cartHome = (CartHome) Proxy.newProxyInstance( CartHome.class.getClassLoader(), new class[]{CartHome.class}, handler ); Cart cart = cartHome.create( "Xiao Min" ); cart.addBook( "Effective Java"); cart.addBook( "Master Java");
Vector books = cart.getContents(); for( int i = 0, n = books.size(); i < n; i++ ) { System.out.println( books.get( i ) ); } |
如果进一步可以将Home接口的代理对象的创建封装起来,就更是完美了。我们将他封装到HomeProxyFactory类中吧。因此,HomeProxyFactory可以写为:
public class HomeProxyFactory { public static Object createHomeProxy( String homeClassName, String remoteClassName, String beanClassName ) { try { InvocationHandler handler = new HomeInvocationHandler ( remoteClassName, beanClassName );
Class homeClass = Class.forName( homeClassName ); return Proxy.newProxyInstance( homeClass.getClassLoader(), new Class[]{homeClass}, handler ); }catch( Exception ex ) { throw new RuntimeException( ex ); } } } |
测试代码引用新创建的HomeProxyFactory的话,测试代码就变为了:
CartHome cartHome = (CartHome) HomeProxyFactory.createHomeProxy( “CartHome”, “Cart”, “CartBean”); Cart cart = cartHome.create( "Xiao Min" ); cart.addBook( "Effective Java"); cart.addBook( "Master Java");
Vector books = cart.getContents(); for( int i = 0, n = books.size(); i < n; i++ ) { System.out.println( books.get( i ) ); } |
是不是很简单?