EJB方法调用框架

  作者:板桥banq

上页

7.3.2  动态代理工厂

在图7-4ServiceClientFactory中封装了获得EJB动态代理实例的全部细节,下面看客户端调用的第一个工厂类ServiceClientFactory代码(程序7-2):
程序7-2
/*
 * 动态代理工厂
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海汲道计算机技术有限公司</p>
 */
public abstract class ServiceClientFactory {
  private static Object initLock = new Object();
  private static String className =
      "com.jdon.bussinessproxy.remote.ServiceFactoryImp";
  private static ServiceClientFactory factory = null;
  //获得本类的Singleton实例
  public static ServiceClientFactory getInstance() {
    if (factory == null) {
      synchronized (initLock) {
        if (factory == null) {
          try {
            //Load the class and create an instance.
            Class c = Class.forName(className);
            factory = (ServiceClientFactory) c.newInstance();
          } catch (Exception e) {
            Debug.logError(" get factory instance error:" + e, module);
            return null;
          }
        }
      }
    }
    return factory;
  }
  //下面有3个需要继承实现的方法
  //设置HTTP连接参数
  public abstract void setHttpServerParam(HttpServerParam httpServerParam);
  //第一次登录login
  public abstract String login(String loginName, String password) throws AuthException;
  //获得EJB的实例

  public abstract Object getService(EJBDefinition eJBDefinition);
}
代码中是将com.jdon.bussinessproxy.remote.ServiceFactoryImp作为ServiceClientFactory的具体实现,在ServiceFactoryImp实现了3个方法(程序7-3):
程序7-3
public class ServiceFactoryImp extends ServiceClientFactory {
  //代理实例的缓存
  private static Map _proxyCache = new Hashtable();

  public void setHttpServerParam(HttpServerParam httpServerParam) {
     httpClient.setHttpServerParam(httpServerParam);
  }

  //首先从缓存中获得代理实例,如果没有,通过动态代理生成
  public Object getService(EJBDefinition eJBDefinition) {
    Debug.logVerbose(" --> enter getService from dynamicProxy", module);
    Object dynamicProxy = _proxyCache.get(eJBDefinition);
    if (dynamicProxy == null) {
      dynamicProxy = getServiceFromProxy(eJBDefinition);
      _proxyCache.put(eJBDefinition, dynamicProxy);
    }
    return dynamicProxy;
  }

  //第一次登录验证
  public String login(String loginName, String password) throws AuthException{
    String loginResult = null;
    try{
      Debug.logVerbose(" --> enter login", module);
      //因为是J2EE容器安全验证,因此虚构一个EJBDefinition
      EJBDefinition eJBDefinition = new EJBDefinition(
          "NoEJB", "com.jdon.bussinessproxy.remote.auth.Authenticator");
      //虚构一个Authenticator实例
      Authenticator authenticator = (Authenticator) getService(eJBDefinition);
      //激活HttpClient的invokeAuth方法
      loginResult = authenticator.login(loginName, password);
    }catch(Exception e){
      throw new AuthException(e);
    }
    return loginResult;
  }

  //通过动态代理获得代理实例
  public Object getServiceFromProxy(EJBDefinition eJBDefinition) {
    RemoteInvocationHandler handler = null;
    Object dynamicProxy = null;
    try {
      Debug.logVerbose(" ---> create a new ProxyInstance", module);
      handler = new RemoteInvocationHandler(eJBDefinition);
      dynamicProxy = Proxy.newProxyInstance(RemoteInvocationHandler.class.
                                            getClassLoader(),
                                            new Class[] {
                                            eJBDefinition.getEJBInterfaceClass()
                                            }, handler);

    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return dynamicProxy;
  }
}
在ServiceFactoryImp中具体实现了getService方法,封装了通过动态代理API获得实例的具体过程。还有另外一个方法login,因为本系统中采取J2EE服务器基于HTTP协议的基本验证,无需具体进行实现验证功能的EJB编程。也就是说,因为没有专门负责验证的EJB Service,那么在login方法中就必须虚构一个EJBDefinition,模拟调用EJB Service。实际上,login这个方法将会在HTTP访问客户端代码HTTPClient中进行特殊处理。
动态代理机制中需要实现InvocationHandler,而在ServiceFactoryImp中的getServiceFromProxy方法中,RemoteInvocationHandler是动态代理API InvocationHandler的具体实现。具体代码如下(程序7-4):
程序7-4
/*
 * 动态代理InvocationHandler
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海汲道计算机技术有限公司</p>
 */
public class RemoteInvocationHandler implements InvocationHandler {
  //获得HTTP访问客户端HttpClient单态实例
  private final static HttpClient httpClient =  HttpClient.getInstance();
  private EJBDefinition eJBDefinition = null;
  public RemoteInvocationHandler(EJBDefinition eJBDefinition) {
    this.eJBDefinition = eJBDefinition;
  }
  //每调用一次远程EJB方法,将激活本方法
  public Object invoke(Object p_proxy, Method method, Object[] args) throws
      Throwable {
    Debug.logVerbose("method:" + method.getName(), module);

    if (method.getName().equals(Authenticator.AUTH_METHOD_NAME))
      //如果是login
      return httpClient.invokeAuth(args);
    else
       return httpClient.invoke(eJBDefinition, method, args);
  }
}
在主要invoke方法中,将远程访问细节委托给HTTPClient的两个方法来实现。如果是login方法调用,将委托给HTTPClient的invokeAuth这个特殊方法来调用,其他则是HTTPClient的invoke方法。
在上面关于login的代码中,都用到了Authenticator接口。它只是一个接口,用来“欺骗”一下动态代理API,在实际运行中,login方法调用已经通过RemoteInvocationHandler的invoke方法进行了分流,Authenticator接口代码很简单,如下:
public interface Authenticator {
    public final static String AUTH_METHOD_NAME = "login";
    public String login(String loginName, String password) throws AuthException;
}
从上面分析中已经知道,RemoteInvocationHandler将真正的远程J2EE服务器有关EJB调用委托给了HTTPClient实现。

7.3.3  肥客户端/服务器架构下实现

在HTTP客户端HTTPClient中将要实现远程J2EE服务器的HTTP连接、EJB调用方法和参数的序列化以及获得远程服务器的处理结果等3个主要功能,同时还要通过J2EE服务器容器安全机制实现登录信息的验证。
HTTP通信离不开两个基本类型:请求(Request)和响应(Response)。在这里需要调用EJB的方法名称、参数类型以及参数数值,然后序列化打包进入请求信号。以7.3.1中举例的框架应用客户端代码为例,在这段代码中,实行了EJB SecurityFacadeLocal的方法getUser()调用,因此要传送的方法名称为“getUser”,因为getUser没有参数,所以参数类型和数值都为空。
建立HTTP请求类HTTPRequest如下:
public class HttpRequest implements Serializable {
    private EJBDefinition eJBDefinition;
    private String methodName;
    private String[] paramTypesName;
    private Object[] args;
    //构造函数
    public HttpRequest(EJBDefinition eJBDefinition, String methodToCall,
                              Class[] paramTypes, Object[] args) {
        this.eJBDefinition = eJBDefinition;
        this.methodName = methodToCall;
        setParamTypes(paramTypes);
        this.args = args;
    }

    public EJBDefinition getEJBDefinition() {        return eJBDefinition;    }
    public void setEJBDefinition(EJBDefinition eJBDefinition) {
        this.eJBDefinition = eJBDefinition;
    }

    public String getMethodName() {        return methodName;    }
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
    …
}
这个HTTPRequest封装了4个参数EJB定义信息EJBDefinition、调用的方法名称、参数类型Class[] paramTypes和参数数值Object[] args。
而HTTP响应信息中只要返回EJB方法的处理结果,这个结果应该是一个对象Object。
public class HttpResponse implements Serializable {
    private Throwable throwable;
    private Object result;

    public HttpResponse(Object result) {        this.result = result;    }
    public HttpResponse(Throwable throwable) {
        this.throwable = throwable;
    }

    public Object getResult() {        return result;    }
    public void setResult(Object result) {
        this.result = result;
    }

    public Throwable getThrowable() {     return throwable;  }
    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;
    }
    public boolean isExceptionThrown() {
        if (throwable != null) return true;
        return false;
    }
}


这个HTTPResponse包含了EJB的处理结果和出错信息。
有了HTTP的请求和响应对象后,因为HTTP可以携带任意对象,因此可以将请求对象HTTPRequest序列化到HTTP协议中去。
由于HTTP远程服务器的连接以及数据发送和接受功能都有比较多的代码,可以封装在一个叫HTTPConnectionHelper的类中,在这个类中实现HTTP服务器的连接、对象序列化、以及反序列化等工作(程序7-5):
程序7-5
/*
 * HTTP连接处理工具帮助类
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海汲道计算机技术有限公司</p>
 */
public class HttpConnectionHelper {
  /**
   * 连接Http Server, 准备传送serialized-object
   */
  public HttpURLConnection connectService(HttpServerParam httpServerParam,
                                          String UserPassword) throws
      Exception {
    HttpURLConnection httpURLConnection = null;
    URL url = null;
    try {
      //获得远程服务器的URL实例
      url = new URL("http", httpServerParam.getHost(),
                    httpServerParam.getPort(), httpServerParam.getServletPath());

      Debug.logVerbose("Service url=" + url, module);
      //打开远程连接
      httpURLConnection = (HttpURLConnection) url.openConnection();
      //提交方式是POST
      httpURLConnection.setRequestMethod("POST");
      httpURLConnection.setDoOutput(true);
      httpURLConnection.setDoInput(true);
      httpURLConnection.setUseCaches(false);
//内容类型是可序列化的对象
      httpURLConnection.setRequestProperty("Content-Type",
          "application/x-java-serialized-object");
      //将登录信息保存在HTTP头部
      String encoded = "Basic " + Base64.encode(UserPassword.getBytes("UTF-8"));
      httpURLConnection.setRequestProperty("Authorization", encoded);

    } catch (Exception ex) {
      Debug.logError(" connectServer " + url + " error: " + ex, module);
      throw new Exception(ex);
    }
    return httpURLConnection;
  }

  /**
   * 将可序列化Object发往HTTP Server
   */
  public void sendObjectRequest(HttpURLConnection httpURLConnection,
                                Object request) throws Exception {
    try {
      //send the request query object to the server
      ObjectOutputStream oos = new ObjectOutputStream(
          httpURLConnection.getOutputStream());
      oos.writeObject(request);
      oos.close();
    } catch (Exception ex) {
      Debug.logError(ex, module);
      throw new Exception(ex);
    }
  }
  /**
   * 从HTTP Server的响应中获取Object
   */
  public Object getObjectResponse(HttpURLConnection httpURLConnection) throws
      Exception {
    Object object = null;
    try {
      ObjectInputStream ois = new ObjectInputStream(
          httpURLConnection.getInputStream());
      object = ois.readObject();
      ois.close();
    } catch (Exception ex) {
      Debug.logError(ex, module);
      throw new Exception(ex);
    }
    return object;
  }
  …
}
HTTPConnectionHelper是个连接帮助工具,用来实现HTTP服务器连接相关必要的工作。
有了HTTP的请求、响应以及连接帮助工具,下面就可以使用这些工具类形成HTTP的访问客户端,HTTPClient类实现思路分两步:
EJB方法调用:生成一个新的HTTPRequest对象,连接远程服务器,将HTTPRequest对象发往远程服务器,接受远程服务器的响应返回结果。代码如下:
/**
 *  本方法由RemoteInvocationHandler直接调用
 */
public Object invoke(EJBDefinition eJBDefinition, Method m, Object[] args) throws
      Throwable {
    Object result = null;
    //生成新的HTTPRequest对象
    HttpRequest request = new HttpRequest(
        eJBDefinition, m.getName(), m.getParameterTypes(), args);
    //从方法invokeHttp返回结果
    result = invokeHttp(request, args);
    return result;
  }

  /**
   * 执行基于HTTP的调用
   */
  public Object invokeHttp(HttpRequest request, Object[] args) throws Throwable {
    HttpResponse httpResponse;
    try {
      HttpConnectionHelper httpConnectionHelper = new HttpConnectionHelper();

      //连接服务器
      HttpURLConnection httpURLConnection = httpConnectionHelper.connectService(
          httpServerParam, getUserPassword(args));
      //发出request
      httpConnectionHelper.sendObjectRequest(httpURLConnection, request);
      //确认request是否被授权
      if (httpURLConnection.getResponseCode() == 401) {
        throw new AuthException(" http Server authentication failed!");
      }
      //接受响应
      httpResponse = (HttpResponse) httpConnectionHelper.getObjectResponse(
          httpURLConnection);
      //断开连接
      httpURLConnection.disconnect();

      return httpResponse.getResult();

    } catch (AuthException ae) {
      throw new AuthException(ae.getMessage());
    } catch (Exception e) {
      String message = "invokeHttp error:";
      Debug.logError(message + e, module);
      throw new RemoteException(message, e);
    }
}
login登录调用:模拟浏览器以Form表单形式向J2EE服务器提交,在HTTP头部保存有登录的用户名和密码。一旦J2EE服务器验证通过,返回特定的欢迎登录字符串,如果没有登录成功,抛出验证异常AuthException。
  /**
   * 用户第一次 Login调用,本方法由RemoteInvocationHandler直接调用
   */
  public Object invokeAuth(Object[] args) throws Throwable {
    Object result = null;
    try {
      HttpConnectionHelper httpConnectionHelper = new HttpConnectionHelper();

      //连接服务器
      HttpURLConnection httpURLConnection = httpConnectionHelper.connectLogin(
          httpServerParam, getUserPassword(args));
      //传递一个参数,可以不用
      java.util.Hashtable params = new java.util.Hashtable();
      params.put("login", "1");
      httpConnectionHelper.sendDataRequest(httpURLConnection, params);

      //检查是否被授权通过
      int status = httpURLConnection.getResponseCode();
      if (status == httpURLConnection.HTTP_UNAUTHORIZED) {
        throw new AuthException(" http Server authentication failed!");
      }
      //接受响应
      result = httpConnectionHelper.getStringResponse(httpURLConnection);

      //断开连接
      httpURLConnection.disconnect();
    } catch (AuthException ae) {
      throw new AuthException(ae.getMessage());
    } catch (Exception e) {
      String message = "invokeAuth error:";
      Debug.logError(message + e, module);
      throw new RemoteException(message, e);
    }
    return result;
}
HTTP客户端是整个框架系统中比较重要的一个环节,它的代码非常类似于XML-RPC调用,可以将传输的对象HTTPRequest改为XML格式的字符串,如请求XML为:
<?xml version="1.0"?>
<methodCall>
  <methodName>getUser </methodName>
  <params>
    <param>
      <value><int>33</int></value>
    </param>
  </params>
</methodCall>
在这个XML格式中也包含方法名、参数类型以及参数数值,如果将这个XML换成Web Services的SOAP,如:
<?xml version="1.0"?>
<SOAP-ENV:Envelope
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <SOAP-ENV:Body>
    <calculateFibonacci   xmlns="http://namespaces.cafeconleche.org/xmljava/ch3/"
      type="xsi:positiveInteger">10</calculateFibonacci>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
这样HTTP客户端就可以拓展为一个SOAP客户端了。

HTTP客户端作为频繁使用的一个模块,为了提高性能,必须设计成多线程操作;为了更加安全,也可以采取HTTP协议。这些技术细节编码调试都比较繁琐,可以使用专门的开源软件包HTTPClient(http://www.innovation.ch/java/HTTPClient/)。使用该软件包可以方便地进行HTTP远程连接,它提供的HTTP连接功能比JDK的HTTPUrlConnection性能有所增强,可以替换使用。

 

 

下页