EJB方法调用框架

  作者:板桥banq

上页

7.3.4  Web层代理Servlet Proxy

HTTP客户端连接J2EE服务器,在服务器端需要设计一个专门的Servlet接受来自远程肥客户端的请求,Servlet是专门处理HTTP请求的。通过专门的Servlet可以将HTTP协议中的数据对象进行反序列化,还原成正常对象,然后直接调用EJB方法,再向客户端返回处理结果。这非常类似于JAX-RPC中,有一个基本servlet类com.sun.xml.rpc.server.http.JAXRPCServlet被应用到处理接受所有的JAX-RPC终端请求。
设计InvokerServlet代码如下(程序7-6):
程序7-6
/**
* 本类是服务器端处理远程客户端发送的调用EJB请求
* 本类功能也可以使用Strut的Action实现
 * 权限验证是使用J2EE的基于HTTP的Basic Auth
 * 使用本类可以作为一个单独的EJB网关服务器
 * <p>Copyright: Jdon.com Copyright (c) 2003</p>
 * <p>Company: 上海汲道计算机技术有限公司</p>
 * @author banq
 * @version 1.0
 */
public class InvokerServlet extends HttpServlet {
  public final static String module = InvokerServlet.class.getName();
  // doGet主要是用于浏览器测试时使用
  protected void doGet(HttpServletRequest request,
                       HttpServletResponse response) throws ServletException,
      IOException {
    response.setContentType("text/html");
    PrintWriter out = new PrintWriter(response.getOutputStream());
    out.println("<html>");
    out.println("<head><title>InvokerServlet</title></head>");
    out.println("<body> ");
    out.println(request.getRemoteUser());
    out.println(" Welcome to login in !!</body></html>");
    out.close();
  }

  protected void doPost(HttpServletRequest request,
                        HttpServletResponse response) throws ServletException,
      IOException {
    if (request.getParameter("login") != null) {
      //如果是login 显示欢迎信息或其他更新通知等信息
      doGet(request, response);
      return;
    }

    //从HttpServletRequest中获得传送的对象
    HttpRequest httpRequest = getHttpServiceRequest(request);
    HttpResponse httpResponse = null;

    try {
      EJBDefinition eJBDefinition = httpRequest.getEJBDefinition();
      String p_methodName = httpRequest.getMethodName();
      Class[] paramTypes = httpRequest.getParamTypes();
      Object[] p_args = httpRequest.getArgs();

      HttpSession session = request.getSession(true);
      //通过HttpSessionProxy调用EJB
      Object object = HttpSessionProxy.handleInvoker (eJBDefinition, session,
                                                  p_methodName,
                                                  paramTypes, p_args);
     //将结果序列化进入Http响应信号
      httpResponse = new HttpResponse(object);
      writeHttpServiceResponse(response, httpResponse);
    } catch (Exception e) {
      Debug.logError(e, module);
    }
  }

  /**
   * 序列化处理结果
   */
  private void writeHttpServiceResponse(HttpServletResponse response,
                        HttpResponse httpResponse) throws Exception {
    OutputStream outputStream = response.getOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(outputStream);
    oos.writeObject(httpResponse);
    oos.close();
  }

  /**
   * 从请求信息中反序列化
   */
  private HttpRequest getHttpServiceRequest(HttpServletRequest request) throws
      Exception {
    ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
    HttpRequest httpServiceRequest = null;
    try {
      httpServiceRequest = (HttpRequest) ois.readObject();
    } catch (ClassNotFoundException e) {
      Debug.logError(e, module);
    }
    ois.close();
    return httpServiceRequest;
  }
}
InvokerServlet其实没有进行实质性动作,只是做了一些序列化和反序列化的工作,将EJB方法调用委托给了HTTPSessionProxy。InvokerServlet其实就是简单处理远程客户端的接待工作,它继承实现了HTTPServlet。
至此,远程肥客户端应用下的主要框架已经基本完成。在这部分框架中完成了下列功能:通过动态代理将客户端方法调用转变成HTTP协议可携带的序列化对象,然后经由HTTP发送到服务器端,服务器端接受后,经过还原处理,递交后台框架再进行处理。
在后台框架中,将同时接受两种客户端的请求。如图7-1所示,Business Proxy将接受来自通过Servlet Proxy转换的远程客户端的EJB方法调用,还有瘦客户端如浏览器通过Web层的MVC(如在Strut的action中)递交的EJB方法调用。因此下面设计实现将开始兼顾这两种需求。

7.3.5  浏览器/服务器架构下实现

瘦客户端的大部分MVC功能是交由J2EE服务器的Web层来解决的。前面章节主要是谈这种体系应用,在Web层使用Strut来实现MVC模式和功能,然后在Strut的action类中向后台Business Proxy递交EJB方法调用。
先看在Strut的action中应该如何进行EJB方法调用,代码如下:
//获得EJB定义实例
EJBDefinition SecurityFacade = new EJBDefinition(
      "java:comp/env/ejb/EJBContollerLocal",
      "com.jdon.security.auth.ejb.SecurityFacadeLocal");

ServiceServerFactory serviceFactory = ServiceServerFactory.getInstance();
SecurityFacadeLocal securityFacadeLocal = (SecurityFacadeLocal)
          serviceFactory.getService(FrameworkServices.SecurityFacade, request);

User user = securityFacadeLocal.getUser();
调用格式和7.3.1节中肥客户端调用差不多,惟一区别是这里从ServiceServerFactory中获得EJB,而肥客户端中是从ServiceClientFactory中获得。
图7-5是瘦客户端方法调用的类关系图:
1
图7-5  瘦客户端/服务器体系的类关系图
与肥客户端方法调用体系不同的是,肥客户端的动态代理RemoteInvocationHandler与HTTPSessionProxy之间要经过HTTP通信处理,瘦客户端的WebInvocationHandler则直接调用了HTTPSessionProxy。
ServiceServerFactory代码实现如下(程序7-7):
程序7-7
public abstract class ServiceServerFactory{
  private static Object initLock = new Object();
  private static String className =
      "com.jdon.bussinessproxy.web.ServiceFactoryImp";
  private static ServiceServerFactory factory = null;

  public static ServiceServerFactory getInstance() {
    if (factory == null) {
      synchronized (initLock) {
        if (factory == null) {
          try {
            //Load the class and create an instance.
            Class c = Class.forName(className);
            factory = (ServiceServerFactory) c.newInstance();
          } catch (Exception e) {
            Debug.logError(" get factory instance error:" + e, module);
            return null;
          }
        }
      }
    }
    return factory;
  }
  //需要实现的抽象方法
  public abstract Object getService(EJBDefinition eJBDefinition,
                                    HttpServletRequest request);
}
ServiceServerFactory指定com.jdon.bussinessproxy.web.ServiceFactoryImp作为其具体子类实现,在com.jdon.bussinessproxy.web.ServiceFactoryImp中通过动态代理实现抽象方法getService。看看动态代理InvocationHandler的继承WebInvocationHandler是如何实现的(程序7-8):


程序7-8
/**
* 动态代理InvocationHandler的实现
 * <p>Copyright: Jdon.com Copyright (c) 2003</p>

 * @author banq
 * @version 1.0
 */
public class WebInvocationHandler implements InvocationHandler {
  private final static String module = WebInvocationHandler.class.getName();
  private HttpSession session = null;
  private EJBDefinition eJBDefinition = null;

  public WebInvocationHandler(EJBDefinition eJBDefinition,
                              HttpSession session) {
    this.eJBDefinition = eJBDefinition;
    this.session = session;
  }

  public Object invoke(Object p_proxy, Method method, Object[] args) throws
      Throwable {
    Debug.logVerbose("method:" + method.getName(), module);
    return handleInvoker(eJBDefinition, session, method, args);
  }

  /**
   * 调用对应的EJB
   */
  public static Object handleInvoker(EJBDefinition eJBDefinition,
                                     HttpSession session,
                                     Method m,
                                     Object[] args) {
    Debug.logVerbose("--> enter EJBProxyHandler getEJBService", module);
    Object result = null;
    try {
      String p_methodName = m.getName();
      Class[] paramTypes = m.getParameterTypes();
      result = HttpSessionProxy.handleInvoker(eJBDefinition, session,
                                              p_methodName, paramTypes, args);
    } catch (Exception ex) {
      Debug.logError("getEJBService error: " + ex, module);
    }
    return result;
  }
}
下面将实现HttpSessionProxy所在的Business Proxy层,这一层主要是实现EJB的方法调用,是整个框架体系的重要核心所在。

7.3.6  核心代理Business Proxy实现

框架核心部分主要是在Business Proxy中实现,尽管向Business Proxy发出请求可能来自两种客户端,但是Business Proxy无需进行这种区别,只要知道它们都是进行EJB服务调用请求的。
Business Proxy由两大部分组成:HTTPSession缓存处理和EJB方法调用。
先看HTTPSession缓存是如何实现的,在上节InvokerServlet中,远程方法调用被委托给HTTPSessionProxy.handleInvoker方法来处理。根据前面框架设计的思想,这里使用HTTPSession作为EJB Session Bean的缓存体,每次调用相同的EJB实例,如果HTTPSession已经存在,就直接使用已经存在的EJBLocalObject或EJBObject,HTTPSessionProxy的代码如下(程序7-9):
程序7-9
/**
 * 使用HTTPSession作为EJB session bean的缓存
 * <p>Copyright: Jdon.com Copyright (c) 2003</p>
 * <p>Company: 上海汲道计算机技术有限公司</p>
 * @author banq
 * @version 1.0
 */
public class HttpSessionProxy {
  private final static String module = HttpSessionProxy.class.getName();
  /**
   * EJB方法调用
   */
  public static Object handleInvoker(EJBDefinition eJBDefinition,
                                     HttpSession session, String p_methodName,
                                     Class[] paramTypes, Object[] args) {
    Debug.logVerbose("--> enter handleInvoker ...", module);
    Object result = null;
    try {
      Object objRef = getEJBServiceRef(eJBDefinition, session);
      Method invokedMethod = objRef.getClass().getMethod(p_methodName,
          paramTypes);
      result = invokedMethod.invoke(objRef, EJBProxyHandler.narrowArgs(args));
      Debug.logVerbose("Invoked Successfully : " + p_methodName, module);
    } catch (Exception ex) {
      Debug.logError("handleInvoker error: " + ex, module);
    }
    return result;
  }
  /**
   * 获取EJBLocalObject或EJBObject
   * 首先从HttpSession中获取,如果没有,再直接生成,然后
   * 放入HttpSession中备下次使用
   */
  private static Object getEJBServiceRef(EJBDefinition eJBDefinition,
                                         HttpSession session) {
    if (session == null) Debug.logVerbose(" --> session is null", module);
    Object ccEjb = null;
    try {
      ccEjb = HttpSessionProxy.getFromCache(eJBDefinition, session);
      if (ccEjb == null) {
        Debug.logVerbose(" --> the service not existed in HttpSession", module);
        ccEjb = EJBProxyHandler.getHomeRef(eJBDefinition);
        HttpSessionProxy.putCache(eJBDefinition, ccEjb, session);
      } else {
        Debug.logVerbose(" --> got the service from the HttpSession" +
                         ccEjb.getClass().getName(), module);
      }
    } catch (Exception ex) {
      Debug.logError(" getEJBServiceRef error:" + ex, module);
    }
    return ccEjb;
  }
  /**
   * 从HttpSession中获取EJB Object
   * 使用ComponentManager保存各种EJB Object
   */
  public static Object getFromCache(EJBDefinition eJBDefinition,
                                    HttpSession session) throws Exception {
    Object ccEjb = null;
    try {
      ComponentManager cm = (ComponentManager) session.getAttribute(
          ComponentManager.getComponentName());
      if (cm != null) {
        Debug.logVerbose(" --> get from Http Session ", module);
        ccEjb = cm.getObject(eJBDefinition.toString());
      }
    } catch (Exception ex) {
      Debug.logError(ex, module);
      throw new Exception(ex);
    }
    return ccEjb;
  }
  /**
   * 将EJB Object 保存到HTTPSession中
   * 使用ComponentManager保存各种EJB Object
   */
  public static void putCache(EJBDefinition eJBDefinition, Object ccEjb,
                              HttpSession session) throws
      Exception {
    try {
      Debug.logVerbose(" --> putCache", module);
      ComponentManager cm = new ComponentManager();
      cm.putObject(eJBDefinition.toString(), ccEjb);
      session.setAttribute(ComponentManager.getComponentName(), cm);
    } catch (Exception ex) {
      Debug.logError(ex, module);
      throw new Exception(ex);
    }
  }
}
HTTPSessionProxy的handleInvoker方法是本系统最主要的方法,使用方法Method调用EJB,通过invokedMethod.invoke方法获得EJB具体方法的处理结果。
其中,EJB的EJBObject或EJBLocalObject(以下简称EJBObject)的引用是保存在HTTPSession中,无论有状态或无状态Session Bean都是使用这种办法。这样,无需客户端提示被调用Session Bean的类型;在代码结构有所改动,无状态Bean变成有状态Bean时,这种办法更显得游刃有余。
但是,HTTPSessionProxy并非将EJB的EJBObject引用直接保存在HTTPSession中,而是使用了ComponentManager来保存EJBObject。
ComponentManager是HTTPSessionBindingListener的一个实现者,是一个HTTPSession的监视器。其作用是能够在HTTPSession超时失效或用户退出调用removeAttribute方法时,自动激活valueUnbound(HTTPSessionBindingEvent event)方法。在这个方法中,实现EJBObject引用清除工作,将那些曾经被保存到HTTPSession中的EJBObject清除干净。这样,垃圾回收机制能够回收这些EJBObject,无疑可以提高内存使用性能,防止内存泄漏。
ComponentManager的代码如下:
public class ComponentManager implements HttpSessionBindingListener {
  private final static String module = ComponentManager.class.getName();
  private static Map sessionEJBs = new HashMap();
  //获取保存的EJBObject引用
  public Object getObject(String key) {
    Object ccEjb = sessionEJBs.get(key);
    return ccEjb;
  }
  //保存EJBObject引用
  public void putObject(String key, Object ccEjb) {
    sessionEJBs.put(key, ccEjb);
  }
  //本对象被放入HttpSession时自动激活
  public void valueBound(HttpSessionBindingEvent event) {
    Debug.logVerbose(" valueBound active", module);
  }
  //本对象被从HttpSession中删除或HttpSession失效时激活
  public void valueUnbound(HttpSessionBindingEvent event) {
    Debug.logVerbose(" unvalueUnbound active", module);
    //将保存的EJBObject删除干净
    Iterator iter = sessionEJBs.keySet().iterator();
    while (iter.hasNext()) {
      String key = (String) iter.next();
      removeEJBObject(key);
      sessionEJBs.remove(key);
    }
  }
  /**
   * destroy the EJB
   */
  private void removeEJBObject(String key) {
    try {
      Object ccEjb = (Object) sessionEJBs.get(key);
      //这个EJB的remove方法需要被任何角色访问,因为此时principle为空
      if (ccEjb instanceof EJBLocalObject)
        ( (EJBLocalObject) ccEjb).remove();
      else if (ccEjb instanceof EJBObject)
        ( (EJBObject) ccEjb).remove();
    } catch (Exception re) {
      // ignore, after all its only a remove() call!
      Debug.logWarning(re, module);
    }
  }
  public static String getComponentName() {
    return module;
  }
}
ComponentManager主要实现了HTTPSessionBindingListener两个方法:valueBound(HTTPSessionBindingEvent event),这是当HTTPSessionProxy 中putCache方法里执行到语句session.setAttribute(ComponentManager.getComponentName(), cm)时,Web容器自动激活valueBound方法;第二个是valueUnbound,这是当保存该对象的HTTPSession过期容器自动失效时,激活该方法,主要用于有状态Session Bean的及时清除工作。
下面是实现本框架系统核心的第二组成部分EJBProxyHandler,它的主要功能是生成EJBObject或EJBLocalObject,代码如下:
/**
* 成EJBObject 或EJBLocalObject
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
 * <p>Company: 上海汲道计算机技术有限公司</p>
 * @author banq
 * @version 1.0
 */
public class EJBProxyHandler {
  private final static String module = EJBProxyHandler.class.getName();
  private final static ServiceLocator sl = ServiceLocator.getInstance();
  private static Map _serviceCache = new Hashtable();
  /**
   * 获得Local或remote的Home接口
   */
  public static Object getHomeRef(EJBDefinition eJBDefinition) {
    Object obj = null;
   Debug.logVerbose("--> enter getHomeRef", module);
    try {
        EJBLocalHome home=
(EJBLocalHome)sl.getLocalHome(eJBDefinition.getJndiName());
        Method createMethod = home.getClass().getMethod( "create", null );
        obj = (EJBLocalObject) createMethod.invoke( home, null );
    } catch (ServiceLocatorException sle) {
      Debug.logError("locator error: " + sle, module);
    } catch (Exception ex) {
      Debug.logError(ex, module);
    }
    return obj;
  }
 
  /**
   * 如果方法参数中有EJB,需要cast这些EJBObject
   * 本方法用于非Local EJB而是远程EJB的调用
   */
  public static Object[] narrowArgs(Object[] p_args) {
    if (p_args == null)
      return null;
    int length = p_args.length;
    Object[] result = new Object[length];
    for (int i = 0; i < length; i++) {
      if (p_args[i] instanceof Remote)
        result[i] = PortableRemoteObject.narrow(p_args[i], EJBObject.class);
      else
        result[i] = p_args[i];
    }
    return result;
  }
  …
}
EJBProxyHandler的代码其实也比较简单,从Home接口中获得create 方法createMethod,然后使用createMethod的invoke方法创建EJBLocalObject或EJBObject,这类似一般EJB调用语句。以下是名为EJBControllerLocal的EJB调用通常做法:
public EJBControllerLocal createEJBControllerLocal() {
    EJBControllerLocal eJBControllerLocal = null;
    try {
      //获得Home接口
      EJBControllerLocal Home home = (EJBControllerLocal Home) sl.getLocalHome(
          JNDINames.EJB_CONTROLLER_EJBHOME);
      //创建EJBLocalObject
      eJBControllerLocal = home.create();
    } catch (ServiceLocatorException slx) {
      Debug.logError(" ServiceLocatorException :" + slx, module);
    } catch (Exception ex) {
      Debug.logError(ex, module);
    }
    return eJBControllerLocal;
  }
使用方法调用EJB和一般调用EJB的区别是:前者可以更抽象,无需指明具体EJB名称,因此适合封装在框架系统中,而直接调用比较直接明了,速度相对比较快。

但是由于本框架采取了缓存技术,每个EJBObject只要一次获取,克服了Reflect在速度性能上的缺点。在最新版本的JDK中,Reflect速度和效率会越来越高。

 

 

下页