TSS提出两种新的EJB调用模式

1. 使用reflection 技术调用EJB :
http://www.theserverside.com/patterns/thread.jsp?thread_id=24019

目前JdonSD框架中的EJB调用框架实际就是这么做的,具体原理见我的书籍《Java实用系统开发指南》中“EJB方法调用框架”章节。

2.为所有的Session Bean 编写统一的Home Interface
http://www.theserverside.com/patterns/thread.jsp?thread_id=22817

这个办法是我在EJB方法调用框架之前使用的一个调用框架,《Java实用系统开发指南》书中也提及到了,也是最容易想到的,但是我发现它在JBuilder的EJB图形开发工具中无效,结果我在bean Imp中增加了新的方法,图形工具无法显示,而且,还需要在ejb-jar.xml配置两次,觉得麻烦,丢弃不用了。

banq,你好。
我想请教相关的几个问题:

1.你提到的第二个方法:“为所有的Session Bean 编写统一的Home Interface ”,是指什么?能说的详细一些吗?(链接有误,无法参考)

2.我也倾向于使用反射方法实现Session Bean的统一调用,但是我的同事提出一个疑问:在客户实现方法调用时难以确定方法的参数,就是说,在隐藏Session Bean调用的同时,丢失了一些必要的细节;同时,也无法进行编译时类型检查了。

究竟是该如何抉择?希望banq能给我指点。谢谢!

第一种方法我已经使用过了,感觉挺好。我用这种方法写了一个无状态会话bean的通用代理,已经在我们的系统中使用,效果不错。


import java.lang.reflect.*;
import java.rmi.*;
import javax.ejb.*;
import javax.naming.*;

import com.gdpost.isp.lis.util.exception.*;
import com.gdpost.isp.lis.util.logging.*;
import com.gdpost.isp.lis.util.naming.*;

/**
* <p>Title: stateless sessionBean的动态代理。这是一个单实例类,
* 只能通过getInstance方法获得这个实例。</p>
* <p>Description: </p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: </p>
* @author wuwei
* @version 1.0
*/

public class DynamicEJBProxy {

// 私有构造函数,防止从外部创建实例
private DynamicEJBProxy () {}

private static DynamicEJBProxy singleton = new DynamicEJBProxy ();
/**
* 得到唯一的实例
* @return 本类唯一的一个实例
*/

public static DynamicEJBProxy getInstance () {
return singleton;
}

/**
* 此方法用来呼叫指定的stateless sessionBean的指定的业务方法。
* 实现过程为:
* <br>&nbsp;&nbsp;1> 根据jndiName和homeInterface找到home接口
* <br>&nbsp;&nbsp;2> 呼叫home接口的create方法,返回remote接口
* <br>&nbsp;&nbsp;3> 呼叫remote接口的业务方法,保存调用结果
* <br>&nbsp;&nbsp;4> 呼叫remote接口的remove方法
* <br>&nbsp;&nbsp;5> 返回第3步的调用接过
* @param jndiName sessionBean的JNDI名字
* @param homeInterface sessionBean的home接口类型
* @param methodName sessionBean的业务方法名字
* @param args sessionBean的业务方法参数
* @return sessionBean的业务方法返回结果
* @throws UserException 被呼叫的sessionBean的业务方法抛出的业务异常
*/

public Object invoke (String jndiName,
Class homeInterface,
String methodName,
Object[] args)
throws UserException {
try {
// 得到home stub
EJBHome homestub =
NamingLocator.getInstance().lookup (
jndiName, homeInterface);

// 调用home stub上的create方法,得到remote stub
Object remotestub = homestub.getClass ().getMethod (
"create", null).invoke (homestub, null);

// 得到remote stub上的要调用的业务方法的参数类型
Class[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; ++i) {
paramTypes[i] = args[i].getClass ();
}

// 调用remote stub上的业务方法
Object returnValue = remotestub.getClass ().getMethod (
methodName, paramTypes).invoke (remotestub, args);

// 调用remote stub上的remove方法
remotestub.getClass ().getMethod (
"remove", null).invoke (remotestub, null);

// 返回调用结果
return returnValue;
}
catch (NamingException e) {
LoggingUtil.loggingExceptionInLIS (e);
throw new UserException (
"399490000", 3, 6, "", "查询jndi出现异常");
}
catch (NoSuchMethodException e) {
LoggingUtil.loggingExceptionInLIS (e);
throw new UserException (
"399490000", 3, 6, "", "ejb没有"+methodName+"方法");
}
catch (IllegalAccessException e) {
LoggingUtil.loggingExceptionInLIS (e);
throw new UserException (
"399490000", 3, 6, "", e.getMessage());
}
catch (InvocationTargetException e) {
// 得到sessionBean的create方法、业务方法以及remove方法抛出的异常
Throwable cause = e.getTargetException();
LoggingUtil.loggingExceptionInLIS (cause);
// 判断异常的类型
if (cause instanceof CreateException) {
throw new UserException (
"399490000", 3, 6, "",
"ejb抛出CreateException");
}
else if (cause instanceof RemoveException) {
throw new UserException (
"399490000", 3, 6, "",
"ejb抛出RemoveException");
}
else if (cause instanceof RemoteException) {
throw new UserException (
"399490000", 3, 6, "",
"ejb抛出RemoteException");
}
else if (cause instanceof UserException) {
// 真正的业务异常
throw (UserException) cause;
}
else {
// 未知类型的异常
throw new UserException (
"399490000", 3, 6, "",
"ejb抛出未知类型的异常");
}
}
}

}

采用反射不错,使得系统更加个灵活多变!

实际上这是两种模式的区别:

使用统一的接口,实际是使用Command模式,这种方式在PetStore中Web调用EJB层采用的方式,优点是Web层不需要知晓EJB的接口,通过XML文件配置。

使用反射机制,实际是一种动态代理,以方法拦截所有的调用,这种方式灵活,需要将EJB的接口方法通知Web层,EJB接口是Web开发小组和EJB开发小组所依赖,也体现了面向接口编程的OO方法吧。

注意:Petstore中使用Command模式还有一个缺点是: 无法实现command方法内事务机制,因为Command类是一个POJO,普通的JavaBeans,不是Session Bean,这对于事务机制要求不高的应用是可以的。

我觉得 wwlhp@jdon 的方法虽然采用了反射机制,但仍然属于Command模式。

我想采用“将EJB的接口方法通知Web层”这种方法,但在声明接口时有问题。

我的系统后台按模块分为几个Session Bean,如何为他们确定一个统一的接口?还是为每个Session Bean分别声明一个接口?

或者统一用一个Session Bean?用一个Session Bean的话,他所包含的方法就太多了!

希望得到Banq的进一步指点。

我觉得使用反射机制就没有了编译期检查,而且可靠性是否也值得考虑。
我的做法是为每一个调用做一个command封装,command server是一个session bean,这样也不会有事务的问题,只是这个时候写command是个繁琐的过程,其实可以通过工具做这些事,包括dto的生成等。

这几天我也正在解决这个问题,看了wwlhp@jdon 的代码,受益。我有两个问题:
1. invoke()首先传入参数args,然后通过
paramTypes = args.getClass ();
得到业务方法的参数。但是如果业务方法的参数是primitive,显然这种方法得到的参数类型是不正确的。例如是int型,则应该如下
paramTypes = Integer.Type;
你是怎么解决这个问题的?

2. 对Entity Bean的访问常常是用Facade模式。我用一个Session Bean做Facade,封装对EntityBean的调用。但是常常不得不为每一个EntityBean相同的方法写各自的实现。比如,每一个EntityBean的Home接口都有一个方法:
Collection findByAll();
返回remote stub的集合,然后将依次调用remote的getter方法将数据填入另一个javabean的Collection,提供给View层使用。
有没有办法使用Reflection写一个proxy实现重用?

楼上的第一个问题是可以解决的,想想一个方法如果有primitive类型的参数,你用reflection是怎么调用的。就是用对应的包装类型就可以了。

sorry,这样却是不行,会找不到方法。

我差不多想通了,使用动态代理+通用接口实现Session Bean的方法调用.
下面是代码,没有考虑异常处理、没考虑多线程:

处理事务的代码还没加上,该在那里控制事务还没想通:<
部分代码参考自wwlhp@jdon

因为还是初学,代码中有什么问题或错误还请前辈及时指出,以免我误入歧途,谢谢!


import javax.naming.*;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;
import javax.ejb.*;
import java.lang.reflect.*;
...
...

class EJBInvocationHandler implements InvocationHandler {
private Object homeStub;
public EJBInvocationHandler(Object o) {
homeStub = o;
}
public Object invoke(Object proxy,Method method,Object[] args){
Object returnValue = null;
Class[] argTypes = null;
try {
Object remoteStub = homeStub.getClass().getMethod("create",
null).invoke(homeStub, null);
// 得到remote stub上的要调用的业务方法的参数类型
if(args != null){
argTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass();
}
}
// 这里可以加入诸如权限控制和日志记录的代码
...
...
// 调用remote stub上的业务方法
returnValue = remoteStub.getClass().getMethod(method.getName(),
argTypes).
invoke(remoteStub, args);
// 调用remote stub上的remove方法
remoteStub.getClass().getMethod(
"remove", null).invoke(remoteStub, null);
}
catch (Exception e) {
e.printStackTrace();
}
return returnValue;
}
}

public class DynamicEJBProxy{
private Context ctx;
public DynamicEJBProxy() {
try{
//create initial context
String url =
"t3://127.0.0.1:7001";
String user = null;
String password = null;

Properties h = new Properties();
h.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
h.put(Context.PROVIDER_URL, url);
ctx = new InitialContext(h);
}
catch(NamingException e){
System.out.print(e.getMessage());
}
}
private static DynamicEJBProxy me = new DynamicEJBProxy();
static public DynamicEJBProxy getInstance() {
return me;
}
public Object createProxy(String jndi,Class clazz){
Object proxy = null;
try {
//look-up home
Object home = ctx.lookup(jndi);
Object homeStub = PortableRemoteObject.narrow(home, home.getClass());
ClassLoader loader = clazz.getClassLoader();

EJBInvocationHandler handler = new EJBInvocationHandler(homeStub);
//创建homeStub的动态代理类
proxy = Proxy.newProxyInstance(loader, new Class[] {clazz},handler);
}
catch(Exception e){
System.out.print(e.getMessage());
}
return proxy;
}

//调用方式
//SessionFacadeInterface是Web与Session Bean的通用接口
//JbPurviewManagerFacade是Session Bean的JNDI名
public static void main(String[] args){
try{
DynamicEJBProxy proxyManager = DynamicEJBProxy.getInstance();
SessionFacadeInterface proxy = (SessionFacadeInterface) proxyManager.createProxy(
"JbPurviewManagerFacade",SessionFacadeInterface.class);

//通过Web与Session Bean的通用接口调用业务方法

JbUserDto[] users = (JbUserDto[]) proxy.getAllUsers();
...
...
}
catch(Exception e){
System.out.print(e.getMessage());
}
}
}

谢谢楼上各位。尽管问题解决了,我还是得马上去弄一本Benq的书来看。

你的第一个问题没有解决吧,如果调用的方法中的参数有int,double等类型就会出现找不到方法的错误呀!