一個簡單的WebService客戶端封裝

之前有提到過在年後做的項目中有一個集中刻錄機管理的系統。簡單來說,這是一個典型的 B/S 系統,用戶在頁面上需要及時瞭解到刻錄機的狀態(各個光驅的使用情況等),並且需要進行刻錄附件、錄像刻錄等操作(從數據庫中獲取存放路徑,使用 samba 共享進行遠程下載)

系統結構

由於刻錄機提供的 SDK 需要使用本地調用的方式,同時考慮到今後可能有多個上層業務系統需要使用相同的刻錄機,所以採用了 業務系統---中間件---刻錄機的方式進行實現,業務系統服務器與中間件服務器通過 WebService 進行數據交換。

劃分好系統結構以後,可以看到業務系統不必再考慮刻錄機 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 圖:

其中WSRequestWSRespnose爲抽象類,這裏主要考慮到在 拼接解析 String 參數時會有許多相同字符串的操作,此類重複的代碼可以在基類中進行處理,例如返回結構中的錯誤信息與錯誤碼,每一次都是相同的格式。所有自行封裝的 Request 和 Response 都需要繼承自 WSRequestWSResponse

代碼實現

先看看目錄結構:

主要看紅框以內的 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());
    }

控制檯輸出:

注:爲了方便調試,在sendRequest()中將實際發送的代碼註釋了,直接人爲拼接了字符串作爲返回結果。在整理代碼的時候對parseJson(JSONObject jsonObject)函數進行了調整,但是 UML 圖還沒來得及修改。
完整代碼以及後續更新可以參考:https://github.com/KevinZY/WSServer
如果發現文章中有錯誤和疏漏之處,或者表述不明確,亦或是您有更好的設計,歡迎在評論中進行回覆_

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章