一个简单的WebService客户端封装

原创文章,转载请注明出处

之前有提到过在年后做的项目中有一个集中刻录机管理的系统。简单来说,这是一个典型的 B/S 系统,用户在页面上需要及时了解到刻录机的状态(各个光驱的使用情况等),并且需要进行刻录附件、录像刻录等操作(从数据库中获取存放路径,使用 samba 共享进行远程下载)

系统结构

由于刻录机提供的 SDK 需要使用本地调用的方式,同时考虑到今后可能有多个上层业务系统需要使用相同的刻录机,所以采用了 业务系统---中间件---刻录机的方式进行实现,业务系统服务器与中间件服务器通过 WebService 进行数据交换。

刻录机操作结构图.png

划分好系统结构以后,可以看到业务系统不必再考虑刻录机 SDK 的调用过程,只需要通过 WebService 与中间件服务器进行通信即可。

WebService 需求分析

首先通过与中间件开发人员的沟通,确定了 WebService的调用过程:中间件只提供一个 doCommand(String str)方法,根据参数 json 字符串的字段信息进行不同的操作。

对于业务系统来说,则需要封装一个调用 WebService 服务的底层实现,它所实现的功能非常简单,就是调用 WebService 方法接收、返回数据。考虑到面向对象的编程方式,将这个 WebService 客户端组件分成三个部分:

  1. Client 描述一个 WebService 客户端,进行doCommand(String str)方法的调用。
  2. Request描述一次 WebService 请求参数并最终由 Client调用其convertToString()方法将其转换为 String类型参数。
  3. Response描述一次请求的返回结果,使用parseString(in String str)方法将返回的字符串转换为自身属性。
    同时,由于 ResponseRequest应该一一对应(实际使用中有多种请求),所以Request需提供getResponse()方法告知其对应返回的 Response 类型。
    根据以上分析,可以画出WSClient 的 UML 图:
WSClient.jpg

其中WSRequestWSRespnose为抽象类,这里主要考虑到在 拼接解析 String 参数时会有许多相同字符串的操作,此类重复的代码可以在基类中进行处理,例如返回结构中的错误信息与错误码,每一次都是相同的格式。所有自行封装的 Request 和 Response 都需要继承自 WSRequestWSResponse

代码实现

先看看目录结构:

WSClient 目录结构.png

主要看红框以内的 WSClient 部分(框外的类在后续会提到),WSConfig 是 WSClient 所需的配置项实体,exception 中包含自定义的异常,为了方便这里只写了一个,external 中放的是通过 axis2的 wsdl2java 工具生成的 java 类,接下来是ClientRequestResponse三个顶级接口,最后是实现了 Client接口的 WSClient 。

三个顶级接口的定义:

//...
public interface Client {
    Response sendRequest(Request req) throws WebServiceClientException;
}

//...
public interface Request {
    Response getResponse();
    String convertToString();
}

//...
public interface Response {
    void parseJson(JSONObject jsonObject) throws JSONException;
    int getErrorCode();
    String getResult();
}

主要的处理逻辑,WSClient 类:

package com.yzhang.webservice;

import com.yzhang.webservice.entity.WSConfig;
import com.yzhang.webservice.exception.WebServiceClientException;
import com.yzhang.webservice.external.GwslibStub;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import java.rmi.RemoteException;


/**
 * Created by yzhang on 2017/4/9.
 */
public class WSClient implements Client{
    private static final Logger logger = Logger.getLogger(WSClient.class);
    private static final long TIMEOUT = 15000;
    private WSConfig config;

    public WSClient(String targetIp, int targetPort){
        config = new WSConfig();
        config.setTargetIp(targetIp);
        config.setTargetPort(targetPort);
    }

    /**
     * send a request and parse response
     * @param request
     * @return
     * @throws WebServiceClientException
     */
    public Response sendRequest(Request request) throws WebServiceClientException {
        // 1. 将 request 转换为 string,准备传输
        String req = request.convertToString();
        logger.info("<< WSClient发送>> -----> "+ req);

        // 2. 调用接口处理,根据实际调用的WebService 进行修改
        GwslibStub.DoCommand cmd = new GwslibStub.DoCommand();
        cmd.setStrXMLReq(req);
        GwslibStub stub;
        GwslibStub.DoCommandResponse res;
        try {
            stub = getGwslibStub();
            Options opts = stub._getServiceClient().getOptions();
            opts.setTimeOutInMilliSeconds(TIMEOUT);
//            res = stub.doCommand(cmd);
        } catch (AxisFault axisFault) {
            logger.error("AxisFault", axisFault);
            throw new WebServiceClientException("连接刻录机服务失败:"+ config.getTargetEndpoint());
        } catch (RemoteException e) {
            logger.error("RemoteException", e);
            throw new WebServiceClientException("连接刻录机服务失败:"+ config.getTargetEndpoint());
        } catch (Exception e){
            logger.error("UnknownException", e);
            throw new WebServiceClientException("连接刻录机服务失败:"+ config.getTargetEndpoint());
        }

        // 3. 解析返回的字符串数据
//        String ret = res.getStrResp();
        String ret ="{ \"result\": ok, \"errorCode\": 0, \"param\": {\"customParam\": don't reapeat yourself}}";
        logger.info("<<WSClient接收>> <----- "+ ret);
        Response response = request.getResponse();
        try{
            JSONObject jsonObject = new JSONObject(ret);
            response.parseJson(jsonObject);
        } catch (JSONException e) {
            logger.error("JSONException", e);
            throw new WebServiceClientException(config.getTargetEndpoint()+ "解析返回结果错误:"+ config.getTargetEndpoint());
        }

        // 4. 处理远端返回错误码
        int errorCode = response.getErrorCode();
        if (errorCode > 0){
            throw new WebServiceClientException(config.getTargetEndpoint()+ "远端返回错误码 errorCode: "+ errorCode);
        }
        return response;
    }

    /**
     * 获取远程调用接口
     * @return
     * @throws AxisFault
     */
    private GwslibStub getGwslibStub() throws AxisFault{
        GwslibStub stub = new GwslibStub(config.getTargetEndpoint());
        return stub;
    }


    public String getTargetIp(){
        return this.config.getTargetIp();
    }

    public int getTargetPort(){
        return this.config.getTargetPort();
    }
}

sendRequest()中将主要操作划分为了四个部分:

  1. 将 request 对象转换为 String 类型
  2. 调用实际的 WebService 接口
  3. 解析返回的数据
  4. 处理远端返回的错误码

除了第二步的调用 WebService 需要使用到具体的类,其他的地方全部都是针对接口进行编程,也就是说整个 WSClient 并不依赖于任何类的具体实现(生成的WebService类除外),而其中的request.convertToString()response.parseJson(jsonObject)等接口函数则需要使用者自己针对不同的业务进行编写。
在 Demo 中写了一个 LongPollingRequest 和与之对应的 LongPollingResponse,其作用是向 WebService 服务端发送一次拉取信息的请求。根据前文的设计,在外部先使用了 WSRequest 、 WSResponse 实现 Request 和 Response 接口,他们的作用是处理一些通用的字段,例如接下来会看到的 errorCoderesult。LongPollingRequest 、 LongPollingResponse 则继承自 WSRequest 和 WSResponse,他们在各自的函数中处理参数的转换。

WSRequest 和WSResponse:

//...
public abstract class WSRequest implements Request{
}

//...
public abstract class WSResponse implements Response{
    private String result;
    private int errorCode;
    public void parseJson(JSONObject jsonObject) throws JSONException {
        try{
            errorCode = jsonObject.getInt("errorCode");
            result = jsonObject.getString("result");
        }catch (NullPointerException e){
            throw new JSONException("未找到指定字段 errorCode或 result", e);
        }
    }


    public int getErrorCode() {
        return errorCode;
    }

    public String getResult() {
        if (result == null) result = "还为收到返回结果";
        return result;
    }
}

LongPollingRequest:

//...
public class LongPollingRequest extends WSRequest {
    private static final long DEFAULT_TIMEOUT = 15;

    private String customParam;

    public Response getResponse() {
         return new LongPollingResponse();
    }

    public String convertToString() {
        JSONObject json = new JSONObject();
        json.putOpt("request", "longpolling");
        JSONObject param = new JSONObject();
        param.putOpt("timeout", DEFAULT_TIMEOUT);
        if (customParam !=null) {
            param.putOpt("customParam", customParam);
        }
        json.putOpt("param", param);
        return json.toString();
    }



    /************getter and setter************/

    public String getCustomParam() {
        return customParam;
    }

    public void setCustomParam(String customParam) {
        this.customParam = customParam;
    }
}

LongPollingResponse:

public class LongPollingResponse extends WSResponse {
    private String customParam;
    public void parseJson(JSONObject jsonObject) throws JSONException {
        super.parseJson(jsonObject);
        try{
            JSONObject param = jsonObject.getJSONObject("param");
            customParam = param.getString("customParam");
        }catch (NullPointerException e){
            throw new JSONException("未找到指定字段 customParam", e);
        }
    }


    public String getCustomParam() {
        return customParam;
    }
}

Demo客户端调用:

public static void main( String[] args ) {
        LongPollingRequest requestOne = new LongPollingRequest();
        LongPollingResponse response = null;

        //only use WSClient to send a request
        requestOne.setCustomParam("test");
        WSClient singleClient = new WSClient("172.16.136.98", 9999);
        try {
            response = (LongPollingResponse) singleClient.sendRequest(requestOne);
        } catch (WebServiceClientException e) {
            logger.error("WebService请求发送失败", e);
        }
        System.out.println(response.getCustomParam());
    }

控制台输出:

控制台输出.png

注:为了方便调试,在sendRequest()中将实际发送的代码注释了,直接人为拼接了字符串作为返回结果。在整理代码的时候对parseJson(JSONObject jsonObject)函数进行了调整,但是 UML 图还没来得及修改。
完整代码以及后续更新可以参考:https://github.com/KevinZY/WSServer
如果发现文章中有错误和疏漏之处,或者表述不明确,亦或是您有更好的设计,欢迎在评论中进行回复_

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,850评论 6 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 一、Java基础 1.写出下面代码的执行结果 2.写出下面代码的执行结果 3.写出下面代码的执行结果 (此题需写出...
    joshul阅读 509评论 0 1
  • 可能是最近快七夕了,所以情侣们都出动尽情秀起恩爱来 晚上出去洗澡的时候,一条街没走到头看见了好多腻腻歪歪在路灯下的...
    answers夏安澤阅读 355评论 0 2