当柳上原的风吹向天际的时候...

真正的快乐来源于创造

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  368 Posts :: 1 Stories :: 201 Comments :: 0 Trackbacks
一.C/S两端的任务分离
   考虑到便于信息接收传递显示的因素,交易系统和QQ类似,采用了传统的C/S模式而不是B/S模式。Client端主要负责取得用户输入和数据显示,而Server端分为DBServer和MsgServer两个,前者负责数据的持久化,后者负责消息的传递。撇开消息服务器MsgServer不谈的话,数据传递主要发生在Client和DBServer之间。
   
二.C/S两端的交互方式
   由于C端只负责数据的输入和显示,它必然需要向DBServer端存取数据,这就有一个信息载体和交互方式的问题。C端需要向DbServer(以下简称DS)传递的信息是多种多样的,简单命令行形式的数据肯定不行,类似JSON的线性形式不够表现树状数据,只有XML才有丰富的表现能力,它无论是简单的线性数据还是复杂的树状数据都能容纳,有了dom4j或是jdom的帮助,解析起来也很方便。交互方式上,由于C可能在广域网中,还可能有防火墙的阻挡,这样Socket长连接就受到一定程度的限制,要是采用WebService问题就解决了,因为WebService的底层协议还是http,也走80端口,不会被防火墙阻挡,这样,DBServer就成了一台放置在公网上的WebService服务器,为各个Client提供Webservice服务。

三.实现WebService的软件选择
   备选有Axis1/2和XFire两种方案,选择的依据主要是效率。通过一段时间的使用,发现XFire的效率确实比Axis1/2高,估测同等调用只占后者的一半左右。其它的易用性,稳定性等没有成为选择依据,因为如果XFire还不行再换其它的软件也来得及,下面的设计保证了系统不会依赖于特定的WebService端软件。

四.WebSevice端的对外接口设计

WebService的对外端口一般是由一个接口和一个实现类组成,实现类中的函数是具体实现,接口是调用者和实现者共同遵守的规约;一般来说如果客户端需要一个函数的话,那么服务器端的接口类要定义这个函数,实现类实现这个函数。这样的方式在交互简单,数据量小的时侯没有问题,且使用很方便,但量变引起质变,如果交互复杂,需要的函数众多,数据量与日俱增的话,问题就来了。其一,这回导致接口类和实现类函数越来越多,体积越来越大,对定位维护修改带来很大的不变;其二,接口类和实现类常会被修改,而开发人员之间的协同等待甚至冲突就日益增多起来,阻滞了开发效率;其三,也是最重要的,系统的可扩展性缺乏,难以动态维护,即使增加多个服务器分担负载,也需要手动修改大量的代码。因此,这种传统的方式在Demo版过后就被放弃了。
新的方式采用的单接口设计,即接口类中只定义一个函数,实现类实现这一个函数,其内部采用反射的方式具体调用在Spring上下文中定义好的Service类来取得结果,输入的参数和返回值都是String,其实质是XML形式的字符串。这样做的好处是:其一,接口类和实现类从设计开始代码就处于稳定状态,以后极少维护,不会越来越大;其二,自然消除了多个开发人员需要修改同一文件的冲突问题;其三,如果服务器负载过重,可以在实现类中根据输入参数的内容做一个分流,把一些任务分配到其它服务器上去,甚至可以采用前端一个分流服务器,后面一堆负责具体业务的服务器的形式。由于只有一个函数,这样修改起来也容易得多。事实上,采用了这种方式后,完成各个流程的程序员只负责前端表现输入,后端的Service类等三个位置的代码,相互间处于平行状态,基本没有交叉,减少了冲突,提高了开发效率。下面是实现类的具体代码。

五.WebService端实现类的具体代码
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.dom4j.DocumentException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

/**
 * 此类中共有方法为WebService对外方法,其它方法为辅助此方法而使用
 *
 * 创建日期:2010-2-9 上午09:19:31
 * 修改时间:2010-2-9 上午09:19:31
 
*/
public class ServiceImpl implements IService{
    
// 日志记录器
    private    static Logger logger = Logger.getLogger(ServiceImpl.class);
    
    
/**
     * 此函数将逐步进行以下任务
     * 1.在log文件中记录请求的XML文本
     * 2.解析文本,得到要访问的类名,方法名,参数
     * 3.使用反射调用类的方法
     * 4.返回结果
     * 
@throws InstantiationException
     
*/
    
public String getResponseXML(String requestXML){
        logger.info(
"接收到客户端的请求XML文本:"+requestXML);
        
// 新建一个包装器
        ResponseXMLPackager packager=new ResponseXMLPackager();
        
        
try {
            
// 使用解析器解析请求XML文本
            RequestXMLParser parser=new RequestXMLParser(requestXML);
            
            
// 从解析器中获取Service服务类
            packager.setServiceName(parser.getServiceName());
            
            
// 从解析器中获取方法名
            packager.setMethodName(parser.getMethodName());
            
            
// 从解析器中获取方法参数
            packager.setArgs(parser.getArgs());
            
            
// 通过Spring得到实例
            Object obj=SpringUtil.getBean(packager.getServiceName());
            logger.info(
"在Spring上下文配置文件中找到了'"+packager.getMethodName()+"'对应的bean.");
            
            
// 得到实例对应的类
            Class<?> cls=obj.getClass();
            
            
// 通过反射得到方法
            Method method = cls.getMethod(packager.getMethodName(), new Class[] {String[].class});
            logger.info(
"通过反射获得了'"+packager.getMethodName()+"'对应的方法.");
            
            
// 通过反射调用对象的方法
            String methodResonseXML=(String)method.invoke(obj,new Object[] {packager.getArgs()});
            logger.info(
"通过反射调用方法'"+packager.getMethodName()+"'成功.");
            
            
/**************************
             * 设置状态,备注及方法反馈结果
             *************************
*/
            String remark
="成功执行类'"+packager.getServiceName()+"'的方法'"+packager.getMethodName()+"'";
            packager.setStatus(ResponseXMLPackager.Status_Success);
            packager.setRemark(remark);
            packager.setMethodResonseXML(methodResonseXML);     
            
            logger.info(remark);
        }
catch (DocumentException e) {
            
// 解析不了从客户端传过来的XML文本时
            
            String remark
="无法解析客户端的请求XML文本:"+requestXML+".";
            
            packager.setRemark(remark);
            packager.setStatus(ResponseXMLPackager.Status_CanNotParseRequestXML);
            
            logger.error(remark);
        }
catch (NoSuchBeanDefinitionException e) {
            
// Spring找不到bean时            
            String remark="无法在Spring上下文定义文件appCtx.xml中找到id'"+packager.getServiceName()+"'对应的bean.";    
                        
            packager.setRemark(remark);
            packager.setStatus(ResponseXMLPackager.Status_CanNotFoundServiceName);
            
            logger.error(remark);
        }        
        
catch (NoSuchMethodException e) {
            
// 找不到方法时
            
            String remark
=("类'"+packager.getServiceName()+"'中没有名为 ‘"+packager.getMethodName()+"’的方法,或是此方法非公有函数,或是参数不是字符串数组形式.");            
            
            packager.setRemark(remark);
            packager.setStatus(ResponseXMLPackager.Status_NotFoundSuchMethod);
            
            logger.error(remark);
        }
catch (IllegalAccessException e) {
            
// 当访问权限不够时
            
            String remark
=("访问类'"+packager.getServiceName()+"'中名为 ‘"+packager.getMethodName()+"’的方法非法,可能原因是当前方法(getResponseXML)对该方法的访问权限不够.");            
            
            packager.setRemark(remark);
            packager.setStatus(ResponseXMLPackager.Status_CanNotAccessMethod);
            
            logger.error(remark);
        }
catch (InvocationTargetException e) {
            
// 当调用的函数抛出异常时
            
            Exception tragetException
=(Exception)e.getTargetException();
            
            
if(tragetException instanceof BreakException){
                
// 程序中断,不能继续进行的情况.比如说用户没有操作权限,要找的目标不存在等.
                packager.setRemark(tragetException.getMessage());
                packager.setStatus(ResponseXMLPackager.Status_Ng);
                
                String remark
=("执行类'"+packager.getServiceName()+"'中名为 ‘"+packager.getMethodName()+"’的方法时被中断,原因是:"+tragetException.getMessage()+".");    
                logger.warn(remark);
            }
            
else{        
                
// 程序运行过程中抛出异常,如空指针异常,除零异常,主键约束异常等.
                String remark=("执行类'"+packager.getServiceName()+"'中名为 ‘"+packager.getMethodName()+"’的方法时,该方法抛出了异常,异常类型为:"+tragetException.getClass().getName()+",异常信息是"+tragetException.getMessage()+".");            
                
                packager.setRemark(remark);
                packager.setStatus(ResponseXMLPackager.Status_MethodThrowException);
                
                logger.error(remark);
            }
        }
                
        
// 向客户端返回响应XML文本
        return packager.toXML();
    }
}


六.Service类中函数的输入和输出
   从上面的代码可见,客户端传过来是一个XML形式的文本,RequestXMLParser类负责从这段文本中解析出具体想调用的配置在Spring上下文中Service类的beanName,类中的具体函数名和函数的参数,然后再用反射的方式调用之。为了调用方便,让每个Service类的具体参数都是String[] 形式的(现在看如果采用类似JSON的形式更好一点),在内部再获得其实际数据,这样,来自客户端的调用就能顺利的到达目的函数中。函数运行完毕后,传出的也是一个XML形式的字符串,这是为了返回数据的方便,到了客户端后,再进行解析变成领域对象类示例。下面代码是一个Service类中函数的例子:
/**
 * 添加一个Tmp对象到数据库
 * 
@param args
 * 
@return
 * 
@throws Exception
 
*/
public String add(String[] args) throws Exception{
    String name
=args[0];
    
    
// 同名检测
    if(hasSameName(name)){
        
throw new BreakException("已经有和"+name+"同名的对象存在了.");
    }
    
    
int age=Integer.parseInt(args[1]);
    
float salary=Float.parseFloat(args[2]);
    String picture
=args[3];
    
    Tmp tmp
=new Tmp(name,age,salary,picture);
    dao.create(tmp);
    
    
return tmp.toXML();



七.领域对象与XML之间的相互转化

由于DB服务器和Client之间传递的是XML形式的文本,但内部使用的都是领域对象,那么,中间需要两次转化过程。以取得一个Tmp对象为例,在服务器端,dao从数据库取得记录后会形成Tmp领域对象的实例,这个实例会转化成XML传到客户端;客户端得到这段XML文本会把它还原成领域对象。以下代码阐述了这两个过程:
// 服务器端领域对象的基类,它的toXML()函数使得实例转化为XML,它的子类只要实现changePropertytoXML()这个抽象接口就能得到此项功能。
public abstract class BaseDomainObj{
    
// 领域对象的唯一识别标志
    protected long id;
    
    
// 名称
    protected String name;
    
    
// 对象对应的记录被添加到数据库的时间(入库时间)
    protected String addTime;
    
    
// 对象对应的记录最近被更新的时间(更新时间)
    protected String refreshTime;
    
    
// 备注
    protected String remark;
    
    
// 节点名
    protected String nodeName;
    
    
// 记录是否有效,若为false则说明无效,常改变此值来隐藏或是显示一个对象
    protected boolean valid;
    
    
/**
     * 无参构造函数
     
*/
    
public BaseDomainObj(){
        
this(0);
    }
    
    
/**
     * 指定id的构造函数
     * 
@param id
     
*/
    
public BaseDomainObj(long id){
        
this.id=id;
        String currTime
=getCurrTime();
        addTime
=currTime;
        refreshTime
=currTime;
        valid
=true;
        remark
="";
    }
    
    
/**
     * 将对象转化为XML形式
     * 
@return
     
*/
    
public String toXML() {
        StringBuilder sb
=new StringBuilder();
        
        sb.append(
"<"+nodeName+">");
        sb.append(
"<id>"+id+"</id>");
        sb.append(
"<name>"+name+"</name>");
        sb.append(
"<addTime>"+addTime+"</addTime>");
        sb.append(
"<refreshTime>"+refreshTime+"</refreshTime>");
        sb.append(
"<remark>"+remark+"</remark>");
        sb.append(
"<valid>"+valid+"</valid>");
        sb.append(changePropertytoXML());        
        sb.append(
"</"+nodeName+">");
        
        
return sb.toString();
    }
    
    
/**
     * 将属性转化为XML,强制子类实现
     * 
@return
     
*/
    
protected abstract String changePropertytoXML();
    
    
/**
     * 取得当前时间
     
*/
    
private static String getCurrTime() {
        Date date 
= new Date();
        Format formatter 
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
return formatter.format(date);
    }

    
/*************************
     * 以下为setter/getter
     ************************
*/
    
    ..
}

// 具体的Tmp对象,重点是changePropertytoXML()这个函数。
public class Tmp extends BaseDomainObj{
    
// 年龄
    private int age;
    
    
// 薪水
    private float salary;
    
    
/**
     * 无参构造函数
     
*/
    
public Tmp(){
        
this("",0,0.0f);
    }
    
    
/**
     * 三参数构造函数
     * 
@param name
     * 
@param age
     * 
@param salary
     
*/
    
public Tmp(String name,int age,float salary){
        nodeName
="Tmp";
        
        
this.name=name;
        
this.age=age;
        
this.salary=salary;
    }
    

    @Override
    
protected String changePropertytoXML() {
        StringBuilder sb
=new StringBuilder();
        
        sb.append(
"<age>"+age+"</age>");
        sb.append(
"<salary>"+salary+"</salary>");
        
        
return sb.toString();
    }

    
/***************************
     * 以下为setter/getter部分
     **************************
*/
    
    
public int getAge() {
        
return age;
    }


    
public void setAge(int age) {
        
this.age = age;
    }


    
public float getSalary() {
        
return salary;
    }


    
public void setSalary(float salary) {
        
this.salary = salary;
    }
}

这样,在得到一个Tmp对象的实例后,调用其toXML函数就能得到这个实例的XML形式表现文本。“六”中的函数就是这样做的。

传出的XML文本实例:
<Tmp>
    
<id>1</id>
    
<name>0</name>
    
<addTime>2010-02-15 23:39:06</addTime>
    
<refreshTime>2010-02-15 23:39:06</refreshTime>
    
<remark></remark>
    
<valid>true</valid>
    
<age>30</age>
    
<salary>15000.0</salary>
</Tmp>


上面这段文本传回到客户端后怎么再把它变成实例呢,有了Apache的BeanUtils包任务就简单多了。下面请看客户端的Tmp类及其基类:
// 客户端Tmp类:
public class Tmp extends BaseDomainObj{
    
    
// 年龄
    private String age;
    
    
// 薪水
    private String salary;
    
    
    @Override
    
public Object[] toArray() {
        
return new Object[]{id,name,age,salary,addTime,refreshTime,valid,remark};
    }


    
public String getAge() {
        
return age;
    }


    
public void setAge(String age) {
        
this.age = age;
    }


    
public String getSalary() {
        
return salary;
    }


    
public void setSalary(String salary) {
        
this.salary = salary;
    }
}

// Tmp类的基类:
public abstract class BaseDomainObj{
    
// 领域对象的唯一识别标志
    protected String id;
    
    
// 名称
    protected String name;
    
    
// 对象对应的记录被添加到数据库的时间(入库时间)
    protected String addTime;
    
    
// 对象对应的记录最近被更新的时间(更新时间)
    protected String refreshTime;
    
    
// 备注
    protected String remark;
    
    
// 记录是否有效,若为false则不该进入
    protected String valid;
    
    
/**
     * ?无参构造函数
     
*/
    
public BaseDomainObj(){
        
    }
    
    
/**
     * 有参构造函数,使用此函数传入一个XML,得到相应对象
     * 
@param xml
     * 
@throws DocumentException
     
*/
    
public BaseDomainObj(String xml) throws DocumentException{
        fromXML(xml);
    }
    
    
/**
     * 将对象转化为数组形式,便于在表格中显示
     * 
@return
     
*/
    
public abstract Object[] toArray();
    
    
/**
     * 使用BeanUtils将XML的节点转化到属性中
     * 
@param xml
     * 
@throws DocumentException
     
*/
    @SuppressWarnings(
"unchecked")
    
public void fromXML(String xml) throws DocumentException{
        Document doc
=DocumentHelper.parseText(xml);        
        
        Element root
=doc.getRootElement();        
        List
<Element> elms=root.elements();
        
        
for(Element elm:elms){
            
try {
                BeanUtils.setProperty(
this,elm.getName(),elm.getText());
            } 
catch (IllegalAccessException e) {
                e.printStackTrace();
            } 
catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    
public String getId() {
        
return id;
    }

    
public void setId(String id) {
        
this.id = id;
    }

    
public String getName() {
        
return name;
    }

    
public void setName(String name) {
        
this.name = name;
    }

    
public String getAddTime() {
        
return addTime;
    }

    
public void setAddTime(String addTime) {
        
this.addTime = addTime;
    }

    
public String getRefreshTime() {
        
return refreshTime;
    }

    
public void setRefreshTime(String refreshTime) {
        
this.refreshTime = refreshTime;
    }

    
public String getRemark() {
        
return remark;
    }

    
public void setRemark(String remark) {
        
this.remark = remark;
    }

    
public String getValid() {
        
return valid;
    }

    
public void setValid(String valid) {
        
this.valid = valid;
    }
}

   
重要的是上面的黑体部分,只要我们保证XML的字段和Tmp对象中的字段是一一对应的,fromXML函数就能保证完成XML到对象的转换,对于负责具体业务的程序员,在代码里如下做就可以了:
String objXML=”;// 从WebService端取出的Tmp对象XML文本
Tmp tmp=new Tmp(objXML);// 这样,对象就出来了.
 

小结:
一.框架设计者一定要定义好框架的任务,限制具体程序员的行为,否则项目的可读性可维护性就是一句空话。
二.框架一定要完成主干的任务的流程,而具体程序员只负责枝节,换言之,具体程序员只该负责简单的规定好了的任务,如某函数的具体实现。
三.好的框架完成后,其他人应该能像填空一样完成任务,要让他们在完成任务时不需要思考具体的来龙去脉。
四.好的框架能让完成任务的程序员尽量平行,减少相互间的交流成本。实际上,框架和工厂流水线的设计某种程度上是相通的。
五.随着数据量和规模的增大,一些问题会逐渐显山露水,这就需要框架设计者有前瞻性的眼光。
六.如果框架已经不能满足需求,带来很多问题时,设计者需要有把前设计推到重来重新组建新框架的勇气和毅力,当断不断,修修补补,蹒跚前行,反受其害。
posted on 2010-05-19 14:22 何杨 阅读(1897) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: