用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();
    }
}


继承ServiceProxy实现自己应用系统的子类,在本项目中,主要是客户资料和订阅信息的数据库操作,因此可以创建一个CustomerService如下:
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的某一项技术精通即可,无形中降低了人员成本。

 

下页