一个简单的WebService客户端封装

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

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

系统结构

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

一个简单的WebService客户端封装_第1张图片
刻录机操作结构图.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 图:
一个简单的WebService客户端封装_第2张图片
WSClient.jpg

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

代码实现

先看看目录结构:

一个简单的WebService客户端封装_第3张图片
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("<> <----- "+ 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
如果发现文章中有错误和疏漏之处,或者表述不明确,亦或是您有更好的设计,欢迎在评论中进行回复_

你可能感兴趣的:(一个简单的WebService客户端封装)