介紹
在工作中使用webservice請求的時候需要單獨寫參數的設定以及請求後的資料解析,于是對這塊進行了封裝,利用了運作時注解和反射機制實作參數封裝,資料傳回使用json格式資料,利用反射實作解析封裝。具體源碼請看這裡:https://github.com/zuolongsnail/WebserviceTest
不足:
1.利用運作時注解和反射在使用時效率不高;
2.參數的封裝和解析可以使用GSON實作;
具體設計
1.請求參數的封裝
這裡在定義webservice接口參數的時候有兩種方法,一是接口需要幾個業務字段就設計幾個參數;二是接口隻設計一個參數,把接口所有業務字段以鍵值對的方式都封裝到一個json串作為唯一的參數傳給服務端,服務端拿到這個參數以後再按鍵值對方式擷取業務字段。
由于webservice服務接收到請求的時候,擷取參數是沒法利用鍵值對的方式取參數值,隻能通過順序取值,我們這裡利用了注解來排序。以下是定義的注解:
/**
* 自定義注解,封裝webservice請求參數
*
* @使用方法 index值為參數順序,name為參數名稱,如果不設定則預設變量名為參數名稱
* @WSReqParam(index = 1) private String uid;
* @WSReqParam(name = "userId") private String uid;
*
* @description @Retention: 定義注解的保留政策
* @Retention(RetentionPolicy.SOURCE) //注解僅存在于源碼中,在class位元組碼檔案中不包括
* @Retention(RetentionPolicy.CLASS) //預設的保留政策,注解會在class位元組碼檔案中存在,但運作時無法獲得
* @Retention(RetentionPolicy.RUNTIME) //注解會在class位元組碼檔案中存在,在運作時可以通過反射擷取到
* @Inherited //說明子類可以繼承父類中的該注解
*
* @Target(ElementType.TYPE) //用于描述類、接口(包括注解類型) 或enum聲明
* @Target(ElementType.FIELD) //用于描述字段、枚舉
* @Target(ElementType.METHOD) //用于描述方法
* @Target(ElementType.PARAMETER) //用于描述參數
* @Target(ElementType.CONSTRUCTOR) //用于描述構造器
* @Target(ElementType.LOCAL_VARIABLE) //用于描述局部變量
* @Target(ElementType.ANNOTATION_TYPE)//注解
* @Target(ElementType.PACKAGE) //用于描述包
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectReqParam {
/**
* 請求參數的順序,适用于webservice請求
*
* @description 使用1,2,3,4,5,6,7....定義參數順序,如果不定義,不會作為參數傳遞
* @return
* @author zuolong
*/
public int index() default 0;
/**
* 請求參數的名稱,适用于webservice和http請求
*
* @description 須與接口文檔中的參數名稱定義相同,不設定則預設變量名為參數的名稱
* @return
* @author zuolong
*/
public String name() default "";
}
那麼我們如何定義參數,這裡打個比方,登入接口需要兩個參數,依次是userAccount和password,給這兩個參數分别設定順序為1和2,index值為參數順序,name為參數名稱,如果不設定則預設變量名為參數名稱,如果參數類中需要添加不作為參數的變量,這裡隻要給變量不設定注解即可,參數類定義如下:
/**
* 登入參數類
*/
public class LoginParams extends BaseReqParams {
@InjectReqParam(index = 1)
public String userAccount;
@InjectReqParam(index = 2)
public String password;
}
通過注解和反射設定webservice請求,代碼如下:
/**
* 通過注解反射參數順序和名稱
*
* @return
* @throws IllegalAccessException
* @description
* @author zuolong
*/
private Map<String, Map<String, String>> injectParamsMap()
throws IllegalAccessException {
// <參數順序index, <參數名稱name, 參數值value>>
Map<String, Map<String, String>> paramsIndexMap = new HashMap<String, Map<String, String>>();
Class<? extends BaseReqParams> cls = mReqParams.getClass();
Field[] fields = cls.getDeclaredFields();
// 循環周遊請求參數對象中的值
if (fields != null && fields.length > 0) {
for (Field field : fields) {
// 判斷變量是否存在指定的注解
if (field.isAnnotationPresent(InjectReqParam.class)) {
// 獲得該成員的annotation
InjectReqParam reqParam = field
.getAnnotation(InjectReqParam.class);
// 封裝參數1.通過注解獲得該參數的順序
int index = reqParam.index();
if (index <= 0) {
continue;
}
// 封裝參數2.通過注解獲得該參數的名稱
String name = reqParam.name();
// 如果沒有設定name則預設變量名為參數名稱
if (TextUtils.isEmpty(name)) {
name = field.getName();
}
// 封裝參數3.擷取參數值
field.setAccessible(true);
Object object = field.get(mReqParams);
String value = null;
if (object != null) {
value = object.toString();
}
// 封裝參數4.存儲參數
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put(name, value);
paramsIndexMap.put(String.valueOf(index), paramMap);
}
}
}
return paramsIndexMap;
}
/**
* 設定webservice請求參數
*
* @param soapObject
* @return
*/
public SoapObject setProperty(SoapObject soapObject) {
try {
Map<String, Map<String, String>> paramsIndexMap = injectParamsMap();
// 封裝參數5.參數排序後進行周遊
TreeSet<String> indexTreeSet = new TreeSet<String>(
new IndexComparator());
indexTreeSet.addAll(paramsIndexMap.keySet());// 把參數順序值放在TreeSet中進行排序
Iterator<String> iterator = indexTreeSet.iterator();
while (iterator.hasNext()) {
String indexKey = iterator.next();
// 封裝參數6.擷取<參數名稱, 參數值>鍵值對,此時map中僅有一對
Map<String, String> paramMap = paramsIndexMap.get(indexKey);
Iterator<String> paramIterator = paramMap.keySet().iterator();
String nameKey = paramIterator.next();
String value = paramMap.get(nameKey);
soapObject.addProperty(nameKey, value);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return soapObject;
}
2.傳回資料的解析
這裡我們定義了四種解析類型,在發起請求時定義好需要的解析類型,當然選擇哪一種解析時更友善得看接口傳回什麼樣的資料,四種解析類型如下:
/**
* 清單解析類型,對應的傳回資料必須嚴格按照以下格式:
* <ul>
* <li>請求成功:
* {
* "success":"true",
* "message":{
* "root":[
* {
* "data1":"資料字段1",
* "data2":"資料字段2"
* },
* {
* "data1":"資料字段1",
* "data2":"資料字段2"
* }
* ]
* }
* }
* <li>請求失敗或錯誤:
* {
* "success":"false",
* "message":"失敗或錯誤資訊"
* }
* </ul>
*/
public static final int LIST_PARSE_TYPE = 1;
/**
* 對象解析類型,對應的傳回資料必須嚴格按照以下格式:
* <ul>
* <li>請求成功:
* {
* "success":"true",
* "message":{
* "data1":"資料字段1",
* "data2":"資料字段2"
* }
* }
* <li>請求失敗或錯誤:
* {
* "success":"false",
* "message":"失敗或錯誤資訊"
* }
* </ul>
*/
public static final int OBJECT_PARSE_TYPE = 2;
/**
* JSON解析類型,對應的傳回資料必須嚴格按照以下格式:
* <ul>
* <li>請求成功:
* {
* "success":"true",
* "message":{
* 可以包含任何JSON資料格式
* }
* }
* <li>請求失敗或錯誤:
* {
* "success":"false",
* "message":"失敗或錯誤資訊"
* }
* </ul>
*/
public static final int JSON_PARSE_TYPE = 3;
/**
* 結果解析類型,對應的傳回資料必須嚴格按照以下格式:
* <ul>
* <li>請求成功:
* {
* "success":"true",
* "message":"請求響應資訊"
* }
* <li>請求失敗或錯誤:
* {
* "success":"false",
* "message":"失敗或錯誤資訊"
* }
* </ul>
*/
public static final int RESULT_PARSE_TYPE = 4;
針對不同的解析類型處理如下:
/**
* 解析響應資料
*
* @param response
* @throws Exception
*/
public void parseRespData(String response) throws JSONException,
IllegalAccessException, InstantiationException {
Message msg = new Message();
if (!TextUtils.isEmpty(response)) {
JSONObject jsonObject = new JSONObject(response);
boolean result = JSONParseUtil.getRequestResult(jsonObject);
if (result) {
if (mParseType == LIST_PARSE_TYPE) {// 清單解析類型資料解析
mResp.pageSum = JSONParseUtil.getPageTotal(jsonObject);
mResp.itemTotal = JSONParseUtil.getItemTotal(jsonObject);
JSONArray array = JSONParseUtil.getDatas(jsonObject);
mResp.setList(mEntityCls, array);
// 如果傳回資料中無字段,則傳回“請求成功無資料”結果
if (mResp.list.size() <= 0) {
msg.what = MessageType.REQ_NODATA;
msg.obj = mContext
.getString(R.string.request_nodata_msg);
// 請求無資料
mHandler.sendMessage(msg);
return;
}
} else if (mParseType == OBJECT_PARSE_TYPE) {// 對象解析類型資料解析
// 資料辨別
String respId = mResp.respId;
// 資料類型
DataType dataType = mResp.dataType;
JSONObject objectMessage = JSONParseUtil
.getMessage(jsonObject);
// 如果傳回資料中無字段,則傳回“請求成功無資料”結果
if (objectMessage.length() <= 0) {
msg.what = MessageType.REQ_NODATA;
msg.obj = mContext
.getString(R.string.request_nodata_msg);
// 請求無資料
mHandler.sendMessage(msg);
return;
}
mResp = mResp.getClass().cast(
JSONParseUtil.reflectObject(mResp.getClass(),
objectMessage));
mResp.respId = respId;
mResp.dataType = dataType;
} else if (mParseType == JSON_PARSE_TYPE) {// JSON解析類型資料解析
msg.what = MessageType.REQ_SUCCESS;
msg.obj = jsonObject;
mHandler.sendMessage(msg);
return;
} else if (mParseType == RESULT_PARSE_TYPE) {// 結果解析類型資料解析
mResp.resultInfo = JSONParseUtil.getResultMsg(jsonObject)
.toString();
}
msg.what = MessageType.REQ_SUCCESS;
msg.obj = mResp;
} else {
msg.what = MessageType.REQ_FAILED;
msg.obj = JSONParseUtil.getResultMsg(jsonObject);
}
} else {
msg.what = MessageType.REQ_FAILED;
msg.obj = mContext.getString(R.string.request_server_error_msg);
}
mHandler.sendMessage(msg);
}
最終通過反射指派到對象:
/**
* 解析後通過反射指派到對象
*
* @param clazz
* @param jsonObj
* @return
* @throws Exception
*/
public static Object reflectObject(Class clazz, JSONObject jsonObj)
throws JSONException, IllegalAccessException,
InstantiationException {
Object instance = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
String attribute = null;
for (Field field : fields) {
attribute = field.getName();
// 判斷傳回資料中是否含有相對字段并且不為“null”
if (jsonObj.has(attribute) && !jsonObj.isNull(attribute)) {
String value = jsonObj.get(attribute).toString();
if (TextUtils.isEmpty(value)) {
field.set(instance, "");
continue;
}
if (field.getType() == int.class) {
field.setInt(instance, Integer.parseInt(value));
} else if (field.getType() == float.class) {
field.setFloat(instance, Float.parseFloat(value));
} else if (field.getType() == double.class) {
field.setDouble(instance, Double.parseDouble(value));
} else if (field.getType() == long.class) {
field.setLong(instance, Long.parseLong(value));
} else if (field.getType() == boolean.class) {
field.setBoolean(instance, Boolean.parseBoolean(value));
} else if (field.getType() == JSONArray.class) {
field.set(instance, new JSONArray(value));
} else {
field.set(instance, field.getType().cast(value));
}
} else {
if (!attribute.equals("serialVersionUID")) {// 過濾序列号類中的serialVersionUID字段
if (field.getType() == int.class) {
field.setInt(instance, 0);
} else if (field.getType() == float.class) {
field.setFloat(instance, 0);
} else if (field.getType() == double.class) {
field.setDouble(instance, 0);
} else if (field.getType() == long.class) {
field.setLong(instance, 0);
} else if (field.getType() == boolean.class) {
field.setBoolean(instance, false);
} else if (field.getType() == String.class) {
field.set(instance, "");
} else {
field.set(instance, null);
}
}
}
}
return instance;
}
使用說明
在實作請求時,可以繼承請求基類再進行封裝,有一定的擴充性。
舉例,接口定義如下:
接口名 | login | ||
接口說明 | 登入請求 | ||
參數序号 | 參數名 | 參數類型 | 參數說明 |
1 | userAccount | String | 賬号 |
2 | password | String | 密碼 |
傳回資訊格式例子 | |||
success字段為true | | ||
success字段為false | |
實作代碼如下:
public class MainActivity extends AppCompatActivity {
private Button btn_start_request;
/**
* webservice請求方法名
*/
private String methodName = "login";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_start_request = (Button) findViewById(R.id.btn_start_request);
btn_start_request.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 請求登入接口,接口定義參見login.interface檔案
LoginParams params = new LoginParams();
params.userAccount = "admin";
params.password = "123456";
sendLoginReqest(params);
}
});
}
/**
* 發起請求
*
* @param params
*/
private void sendLoginReqest(BaseReqParams params) {
// 使用自定義請求
// LoginRequest loginRequest = new LoginRequest(this, methodName, loginHandler, params, new LoginResp(), null, BasicRequest.JSON_PARSE_TYPE);
// loginRequest.sendRequest();
// 使用基類請求
BaseWebserviceRequest loginRequest = new BaseWebserviceRequest(this, methodName, loginHandler, params, new LoginResp(), null, BasicRequest.OBJECT_PARSE_TYPE);
loginRequest.sendRequest();
}
/**
* 資料傳回以後在主線程進行業務處理
*/
private Handler loginHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
int what = msg.what;
switch (what) {
case MessageType.REQ_SUCCESS:
if (msg.obj instanceof LoginResp) {
LoginResp resp = (LoginResp) msg.obj;
String uid = resp.uid;
String userName = resp.userName;
String phoneNo = resp.phoneNo;
String gender = resp.gender;
String headUrl = resp.headUrl;
}
break;
default:
Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_SHORT).show();
break;
}
}
};
}