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

04-02-29 banq
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配置两次,觉得麻烦,丢弃不用了。

holykeeper
2004-07-03 21:35
banq,你好。

我想请教相关的几个问题:

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

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

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

wwlhp@jdon
2004-07-04 11:19
第一种方法我已经使用过了,感觉挺好。我用这种方法写了一个无状态会话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抛出未知类型的异常");
            }
        }
    }

}

zhuam
2004-07-05 08:49
采用反射不错,使得系统更加个灵活多变!

banq
2004-07-05 09:25
实际上这是两种模式的区别:

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

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

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

holykeeper
2004-07-06 17:12
我觉得 wwlhp@jdon 的方法虽然采用了反射机制,但仍然属于Command模式。

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

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

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

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

windjp
2004-07-07 11:41
我觉得使用反射机制就没有了编译期检查,而且可靠性是否也值得考虑。

我的做法是为每一个调用做一个command封装,command server是一个session bean,这样也不会有事务的问题,只是这个时候写command是个繁琐的过程,其实可以通过工具做这些事,包括dto的生成等。

sanux
2004-07-07 14:40
这几天我也正在解决这个问题,看了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实现重用?

wwlhp@jdon
2004-07-08 18:45
楼上的第一个问题是可以解决的,想想一个方法如果有primitive类型的参数,你用reflection是怎么调用的。就是用对应的包装类型就可以了。

wwlhp@jdon
2004-07-08 18:49
sorry,这样却是不行,会找不到方法。

holykeeper
2004-07-09 07:54
我差不多想通了,使用动态代理+通用接口实现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());
    }
  }
}
<p>

banq
2004-07-12 20:56
非常不错,和我的书籍《Java实用系统开发指南》第七章 EJB方法调用框架思路一致的。

sanux
2004-07-19 13:37
谢谢楼上各位。尽管问题解决了,我还是得马上去弄一本Benq的书来看。

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

猜你喜欢
2Go 1 2 下一页