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;
}
}
有了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客户端了。
下页