用EJB开发订阅信息系统
作者:板桥banq
上页
5.4.2 框架的实现
在该框架中,比较重要的核心类是ServiceProxy类。在该类中要做到一个接口的作用,承前启后,同时又不能与前后过分耦合,这是通过两种方式达到的:首先通过XML配置文件和Web层建立对应关系,这样是可扩展而且安全的;然后通过JNDI调用EJB层的一个Facade总类,这个Facade总类在具体应用系统中是可以定义的。
ServiceProxy代码如下:
/**
* EJB Service代理类
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>
* @author banq
* @version 1.0
*/
public abstract class ServiceProxy {
//最终递交处理的EJB Facade总类
protected EJBControllerLocal eJBControllerLocal = null;
public EJBControllerLocal getEJBControllerLocal() {
if (this.eJBControllerLocal == null)
createEJBControllerLocal();
return this.eJBControllerLocal;
}
//需要具体应用系统具体实现Facade总类,和EJB层发生联系
public abstract void createEJBControllerLocal();
//需要具体应用系统具体实现,和Web层发生联系
public abstract void perform(Event e);
}
抽象方法perform的输入参数是Event,Event作为贯穿Web层和EJB层的“信使”,主要封装了具体的DTO对象。
Event的发生起源地是Web层。从Strut的Action送达中间网关层ServerProxy,再送达具体EJB实现处理,处理完成后,Web层可以直接从Event中了解处理情况,如果有错误发生则显示错误。Event接口如下:
public interface Event extends java.io.Serializable{
public static final int QUERY = 1;
public static final int EDIT = 2;
public static final int CREATE = 3;
public static final int DELETE = 4;
//Web层的Action名称
public void setActionName(String actionName);
public String getActionName();
//具体Action类型,可以是QUERY、EDIT、CREATE或 DELETE
public void setActionType(int actionType);
public int getActionType();
//处理过程中的错误信息
public void setErrors(String errors);
public String getErrors();
public String getEventName();
}
Event记载着发出该Event的Web层的Action名称,同时记录着具体的操作类型,送达EJB层处理后,如果有错误,例如没有发现该用户等,可以通过setErrors方法保存到Event实例中。
ServiceProxy是一个实现Command模式的动态抽象类,其具体实现依赖于具体应用系统的实现;但是,ServiceProxy实例的动态获取将是通过抽象工厂模式实现的。
使用工厂模式的好处是可以在框架中封装ServiceProxy具体生产过程,因为ServiceProxy作为一个核心类,创建它可能需要一些附带工作,如从配置文件中获取ServiceProxy的具体实现,对ServiceProxy产品中的EJB Facade总类要实现有状态的管理等,如果将来加入缓冲Cache功能,也可以涉及ServiceProxy的创建,所以使用工厂模式将这些过程封装起来,有利于动态扩展。
建立一个ProxyFactory工厂,专门用来生产ServiceProxy产品:
/**
* 产品ServiceProxy的生产工厂
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海极道计算机技术有限公司</p>
* @author banq
* @version 1.0
*/
public abstract class ProxyFactory {
private static ComponentManager cm = null;
private static Object initLock = new Object();
private static String className = "com.jdon.controller.ProxyFactoryImp";
private static ProxyFactory factory = null;
//以Singleton获得本工厂实例
public static ProxyFactory getInstance() {
if (factory == null) {
synchronized (initLock) {
if (factory == null) {
try {
//Load the class and create an instance.
Class c = Class.forName(className);
factory = (ProxyFactory) c.newInstance();
cm = new DefaultComponentManager();
} catch (Exception e) {
Debug.logError(" get factory instance error:" + e, module);
return null;
}
}
}
}
return factory;
}
/**
* 外界使用的方法,获得一个ServiceProxy
* @param e
* @param request
* @return
*/
public ServiceProxy getServiceProxy(Event e, HttpServletRequest request) throws
Exception {
Debug.logVerbose(" --> enter getServiceProxy", module);
ServiceProxy serviceProxy = null;
try {
serviceProxy = getServiceProxy(e);
Debug.logVerbose(" --> enter ComponentManager setup ", module);
HttpSession session = request.getSession(true);
//check if componentManager is in session ?
ComponentManager cmSession =
(ComponentManager) session.getAttribute(Constants.COMPONENT_MANAGER);
if (cmSession == null) {
//触发valueBound方法
session.setAttribute(Constants.COMPONENT_MANAGER, this.cm);
this.cm.setEJBController(session, serviceProxy.getEJBControllerLocal());
}
} catch (Exception ex) {
Debug.logError(" getServiceProxy error:" + ex, module);
throw new Exception(ex);
}
return serviceProxy;
}
//获取ServiceProxy实例,需要实现
//这里是使用ProxyFactoryImp实现,从XML配置文件中获取
//ServiceProxy实例
protected abstract ServiceProxy getServiceProxy(Event e) throws Exception;
}
在本ProxyFactory封装了两大功能:
将com.jdon.controller.ProxyFactoryImp定义为本工厂的具体实现;
启动ComponentManager组件。
ProxyFactoryImp主要是从XML配置文件eventmappings.xml中寻找出相应的ServieProxy实例,从而实现ProxyFactory中抽象方法getServiceProxy(Event e))。
ProxyFactoryImp只是使用XML配置文件获取ServiceProxy实例的一个实现,可以根据应用系统的具体特点定制自己的ProxyFactory实现,这也是工厂模式的好处。
ProxyFactoryImp代码如下:
/**
* 工厂具体实现者
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海极道计算机技术有限公司</p>
* @author banq
* @version 1.0
*/
public class ProxyFactoryImp extends ProxyFactory {
public final static String module = ProxyFactoryImp.class.getName();
private final static String EVENT_MAPPINGS_FILENAME = "eventmappings.xml";
private final static String EVENT_MAPPING = "event-mapping";
private final static String EVENT_NAME = "web-action-name";
private final static String ACTION_CLASS = "service-proxy-class";
private HashMap actions = new HashMap();
private Object getEventMapping(String name) {
loadMapping();
return actions.get(name);
}
/**
* 通过XMLUtil工具取得eventmappings.xml映射到内存中
*/
private void loadMapping() {
try {
Map mappings = XmlUtil.loadMapping(EVENT_MAPPINGS_FILENAME,
EVENT_MAPPING,
EVENT_NAME,
ACTION_CLASS);
Iterator i = mappings.keySet().iterator();
while (i.hasNext()) {
String key = (String) i.next();
String value = (String) mappings.get(key);
actions.put(key, ObjectCreator.createObject(value));
}
} catch (Exception ex) {
Debug.logError(" error: " + ex, module);
}
}
/**
* 获得ServiceProxy一个实例
* @param e
* @return
*/
public ServiceProxy getServiceProxy(Event e) throws Exception {
if (checkEvent(e))
return null;
String acitonName = e.getActionName();
ServiceProxy serviceProxy = null;
Object o = null;
try {
o = actions.get(acitonName);
if (o == null)
o = getEventMapping(acitonName);
if (o == null)
Debug.logError(" not found the action class: key :" + acitonName +
" check " + EVENT_MAPPINGS_FILENAME, module);
serviceProxy = (ServiceProxy)o;
} catch (Exception ex) {
Debug.logError(" getServiceProxy error: " + ex, module);
Debug.logError(" acitonName is " + acitonName , module);
Debug.logError(" ServiceProxy is " + o.getClass().getName() , module);
throw new Exception(ex);
}
return serviceProxy;
}
//检查Event相关参数是否正确设置
private boolean checkEvent(Event e) {
boolean success = false;
if (UtilValidate.isEmpty(e.getActionName())) {
Debug.logError(" No ActionName in the event:" + e.getEventName(), module);
success = true;
}
if (e.getActionType() == 0) {
Debug.logError(" No ActionType in the event:" + e.getEventName(), module);
success = true;
}
return success;
}
}
ProxyFactoryImp主要是实现eventmappings.xml文件的操作,eventmappings.xml文件的内容结构如下:
<eventmappings>
<event-mapping>
<web-action-name>YOUR_WEB_ACTION_NAME</web-action-name>
<service-proxy-class>YOUR_SERVICE_PROXY_CLASS</service-proxy-class>
</event-mapping>
<event-mapping>
<web-action-name>YOUR_WEB_ACTION_NAME</web-action-name>
<service-proxy-class>YOUR_SERVICE_PROXY_CLASS</service-proxy-class>
</event-mapping>
…
</eventmappings >
在这个XML配置文件中,可以动态加入或修改Web层中前台Action类和ServiceProxy类的具体实现之间的对应关系。
通过ProxyFactoryImp,ProxyFactory获取了ServiceProxy实现实例,与Web层建立了松散的联系,下一步是建立相关EJB层的一些操作。
ProxyFactory实现的第二个功能是启动ComponentManager,ComponentManager组件是为了实现对有状态Session Bean的支持。如果EJB层的Facade总类EJBController是一个有状态Session Bean,那么就要锁定这个有状态Session Bean。在用户退出后,还要及时销毁它,ComponentManager接口实际是HttpSessionBindingListener子接口:
public interface ComponentManager extends HttpSessionBindingListener{
public EJBControllerLocal getEJBController(HttpSession session);
public String getComponentName();
}
DefaultComponentManager是ComponentManager的真正的具体实现者:
public class DefaultComponentManager implements ComponentManager {
private final static String module = ComponentManager.class.getName();
protected final static ServiceLocator sl = ServiceLocator.getInstance();
//
private static Map sessions = new HashMap();
private static Map sessionEJBs = new HashMap();
public EJBControllerLocal getEJBController(HttpSession session) {
String key = getSessionEJBsKey(session);
EJBControllerLocal ccEjb = (EJBControllerLocal) sessionEJBs.get(key);
return ccEjb;
}
//将有状态EJBController保存起来
public void setEJBController(HttpSession session, EJBControllerLocal ccEjb) {
String key = getSessionEJBsKey(session);
sessionEJBs.put(key, ccEjb);
}
//Http session触发,保存本Session
public void valueBound(HttpSessionBindingEvent event) {
Debug.logVerbose(" valueBound active", module);
HttpSession session = event.getSession();
sessions.put(session.getId(), session);
}
//清除有状态EJBController或其他保存的状态
public void valueUnbound(HttpSessionBindingEvent event) {
Debug.logVerbose(" unvalueUnbound active", module);
HttpSession session = event.getSession();
sessions.remove(session.getId());
String key = getSessionEJBsKey(session);
EJBControllerLocal ccEjb = (EJBControllerLocal) sessionEJBs.get(key);
try {
ccEjb.remove();
sessionEJBs.remove(key);
} catch (Exception re) {
// ignore, after all its only a remove() call!
Debug.logWarning(re, module);
}
}
/**
* the count of online users
* @return
*/
public int getSessionsSize() {
return sessions.size();
}
private String getSessionEJBsKey(HttpSession session) {
StringBuffer buffer = new StringBuffer(session.getId());
buffer.append(Constants.EJB_CONTROLLER);
return buffer.toString();
}
public String getComponentName() {
return module;
}
}
该框架通过下列几点达到设计目标。
ServiceProxy作为执行Command模式中的接口,说明该框架是以Command为核心的。
ServiceProxy是从Strut中的Action中分离出来,作为Web层和EJB层之间的网关,实现了与前台Web层以及后台EJB的解耦分离,通过eventmappings.xml配置可以与前台Web层达成对应关系;与EJB层对应关系是通过EJBController接口的具体子类实现来完成的。
通过ComonetManager可以实现有状态行为。
5.4.3 框架的使用
该框架比较方便,只要完成三步就可以:首先,将基本对象包装成Event事件;其次,实现ServiceProxy;最后,配置ServiceProxy和前台Action的对应表eventmappings.xml。下面分别详细描述。
本项目中,Customer是一个域的基本对象,因此建立CustomerEvent将Customer封装起来,作为事件送达后台各层。
CustomerEvent继承实现了EventSupport,而EventSupport则是Event的一个实现,CustomerEvent代码如下:
public class CustomerEvent extends EventSupport {
//基本对象Customer
private Customer customer = null;
public CustomerEvent(String actionName){
this.actionName = actionName;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Customer getCustomer() {
return this.customer;
}
public String getEventName(){
return getClass().getName();
}
}
public class CustomerService extends ServiceSupport {
private final static String module = CustomerService.class.getName();
//实现perform方法
public void perform(Event e) {
Debug.logVerbose("-->enter Service event: " + e.getEventName(), module);
CustomerEvent ce = (CustomerEvent) e;
switch (ce.getActionType()) {
case Event.QUERY:
performQuery(ce);
break;
case Event.CREATE:
Debug.logVerbose("-->enter create userId: ", module);
getCustomerManagerLocal().createCustomer(ce);
break;
case Event.EDIT:
Debug.logVerbose("-->enter edit ", module);
break;
default:
Debug.logVerbose("no this actionType " + e.getActionType(),
module);
break;
}
}
private CustomerManagerLocal getCustomerManagerLocal() {
CustomerManagerLocal customerManagerLocal = null;
try {
customerManagerLocal = (CustomerManagerLocal) eJBControllerLocal;
} catch (Exception ex) {
Debug.logError(ex, module);
}
return customerManagerLocal;
}
}
在CustomerService实现了ServiceProxy的perform方法。ServiceProxy的另外一个抽象方法EJBController的创建是由ServiceSupport实现的。由于本项目比较简单,只需要和一个Facade类CustomerManager打交道,因此可以建成一个统一的ServiceSupport完成EJB的创建。
public abstract class ServiceSupport extends ServiceProxy {
private final static String module = ServiceSupport.class.getName();
public void createEJBControllerLocal() {
Debug.logVerbose(" -->enter getEJBController()", module);
try {
CustomerManagerLocalHome home = (CustomerManagerLocalHome) sl.
getLocalHome(JNDINames.EJB_CONTROLLER_EJBHOME);
this.eJBControllerLocal = home.create();
} catch (ServiceLocatorException slx) {
Debug.logError(" ServiceLocatorException :" + slx, module);
} catch (Exception ex) {
Debug.logError(ex, module);
}
}
}
代码方法的实现已经完成,该框架使用还需要配置文件的设置。第三步是配置eventmappings.xml,建立CustomerService和前台Strut Action的对应关系。
<eventmappings>
<event-mapping>
<web-action-name>com.jdon.samples.web.CustomerAction</web-action-name>
<service-proxy-class>
com.jdon.samples.service.CustomerService
</service-proxy-class>
</event-mapping>
<event-mapping>
<web-action-name>
com.jdon.samples.web.SearchCustomerAction
</web-action-name>
<service-proxy-class>
com.jdon.samples.service.CustomerService
</service-proxy-class>
</event-mapping>
</eventmappings >
还有一个配置是EJB指向在web.xml中的配置,在web.xml加入:
<ejb-local-ref>
<ejb-ref-name>ejb/EJBController</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>com.jdon.samples.ejb.CustomerManagerLocalHome</local-home>
<local>com.jdon.samples.ejb.CustomerManagerLocal</local>
<ejb-link>CustomerManager</ejb-link>
</ejb-local-ref>
到这里基本完成了接口框架的实现。下面看看在Strut的Action这样的前台Web中是如何调用该接口框架的。CustomerAction是Strut的一个Action,经过改写如下:
public class CustomerAction extends Action {
private final static String module = CustomerAction.class.getName();
public ActionForward execute(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest request,
HttpServletResponse response) throws
Exception {
FormBeanUtil.remove(actionMapping, request);
CustomerForm customerForm = (CustomerForm) actionForm;
//创造一个CustomerEvent
CustomerEvent ce = createCustomerEvent(customerForm, request);
//交由接口控制框架处理
ServiceProxyHandler.perform(ce, request);
if (ce.getErrors() == null) {
return actionMapping.findForward("createCustomOk");
} else {
//如果处理结果不为空,显示错误
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError(ce.getErrors()));
saveErrors(request, errors);
return actionMapping.getInputForward();
}
}
/**
* create a UserEvent
*/
private CustomerEvent createCustomerEvent(CustomerForm form,
HttpServletRequest request) throws
Exception {
String action = form.getAction();
CustomerEvent e = new CustomerEvent(module);
Customer customer = new Customer();
try {
//从前台输入的表单中获取数据转为Customer实例
PropertyUtils.copyProperties(customer, form);
e.setCustomer(customer);
e.setActionType(FormBeanUtil.actionTransfer(action));
} catch (Exception ex) {
Debug.logError("getUserEvent error" + ex, module);
throw new Exception(ex);
}
return e;
}
}
在CustomerAction中没有任何EJB调用代码,而且通过一行语句ServiceProxyHandler.perform(ce, request)就将核心逻辑运算交由后台处理了,显得简洁而有效,同时CustomerAction这样的代码结构可以应用到其他Action实现中。比如UserAction或OrderAction,只要将其中的Customer替换成Order;CustomerForm替换成OrderForm;将CustomerEvent替换成CustomerEvent,其他流程和结构都无需变化,这种模板式的编程方法非常适合流水线的操作,无需更专业的知识,降低了人员成本。
采取这种接口框架对软件工程管理带来了实质性的进步,它切断了EJB和Web的直接联系。在Domain Model确定后,负责EJB的开发队伍可以和负责Web的开发队伍同时进行,提高了效率。
最主要的是,EJB开发队伍的人员无需了解或掌握Web层的技术,反之亦然。这样无需开发人员很高的的专业水平,只要求他们对Web或EJB的某一项技术精通即可,无形中降低了人员成本。
下页