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是瘦客户端方法调用的类关系图:
图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):
/**
* 动态代理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名称,因此适合封装在框架系统中,而直接调用比较直接明了,速度相对比较快。
下页