遠程過程調用是什麼呢?通俗來說就是程序員在訪問遠程資源的時候,就跟在本地訪問一樣。實現遠程過程調用遠沒有想象那麼複雜,簡單來說就是將放在業務裏面的調用外部系統邏輯的代碼獨立開來,調用者不需要考慮遠程調用的實現細節,與在本地調用一個服務層一樣。很多廠家的sdk也都是這樣的方式,就拿騰訊雲的短信爲例,看似只需要填寫短信的相關配置,然後調用其給定的方法就行了,其實還內含了例如網絡傳輸,序列化與反序列化的東西,把這些問題給屏蔽掉了。
當然,簡單的對http接口調用進行封裝,算不上是真正的遠程過程調用,只是借用RPC的概念,通過對調用接口的封裝,屏蔽掉不同接口調用者網絡實現和序列化與反序列化的實現差異。減少不必要的調試。
我現在是按照下面的方式實現的。首先定義一個通用的網絡請求方法,這是一個簡單的工具類
public class NetWorkUtil{
public static final int HTTP_METHOD_GET = 1;
public static final int HTTP_METHOD_HEAD = 2;
public static final int HTTP_METHOD_POST = 3;
public static final int HTTP_METHOD_PATCH = 4;
public static final int HTTP_METHOD_PUT = 5;
public static final List<String[]> reqHeaderList = Arrays.asList(new String[][]{
{"Accept-Encoding", "gzip, deflate, sdch"},
{"Accept-Language", "zh-CN,zh;q=0.8"},
{"Connection", "keep-alive"},
{"User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36"}
});
/**
* 獲取 http 請求頭
*
* @param xAuth acunetix key
* @return http 請求頭
*/
public static Map<String, String> getReqHeaders(String xAuth) {
Map<String, String> reqHeaders = new HashMap<>();
reqHeaderList.forEach(header -> {
reqHeaders.put(header[0], header[1]);
});
reqHeaders.put("X-Auth", xAuth);
return reqHeaders;
}
/**
* http 應答
*/
public static class HttpResponse {
public CloseableHttpResponse response;
public String body;
}
/**
* 獲取應答
*
* @param httpClient http 客戶端
* @param method 請求方法
* @param httpRequest http 請求
* @return http 響應
*/
public static HttpResponse getResponse(CloseableHttpClient httpClient, int method, HttpRequestBase httpRequest) {
CloseableHttpResponse httpResponse = null;
String body = null;
try {
httpResponse = httpClient.execute(httpRequest);
if (method != HTTP_METHOD_HEAD) {
HttpEntity httpEntity = httpResponse.getEntity();
if (Objects.nonNull(httpEntity)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "utf-8"));
String str = null;
StringBuffer stringBuffer = new StringBuffer();
while (Objects.nonNull(str = reader.readLine())) {
if (StringUtils.isBlank(str)) {
continue;
}
stringBuffer.append(str);
}
body = stringBuffer.toString();
}
}
} catch (Exception e) {
e.printStackTrace();
httpResponse = null;
} finally {
try {
httpResponse.close();
} catch (Exception ex) {
httpResponse = null;
}
try {
httpClient.close();
} catch (Exception ex) {
httpResponse = null;
}
}
if (Objects.isNull(httpResponse)) {
return null;
}
HttpResponse response = new HttpResponse();
response.response = httpResponse;
response.body = body;
return response;
}
/**
* 獲取 Http 請求對象
*
* @param url 要訪問的 url
* @param method 訪問的方式
* @param headers 請求頭
* @param body 請求內容
* @return http 請求對象
*/
public static HttpRequestBase newHttpRequest(String url, int method, Map<String, String> headers, Object body) {
HttpRequestBase httpRequest = null;
switch (method) {
case HTTP_METHOD_GET:
httpRequest = new HttpGet(url);
break;
case HTTP_METHOD_HEAD:
httpRequest = new HttpHead(url);
break;
case HTTP_METHOD_POST:
httpRequest = new HttpPost(url);
break;
case HTTP_METHOD_PATCH:
httpRequest = new HttpPatch(url);
break;
case HTTP_METHOD_PUT:
httpRequest = new HttpPut(url);
break;
default:
return null;
}
if (httpRequest instanceof HttpPost || httpRequest instanceof HttpPatch || httpRequest instanceof HttpPut) {
try {
if (body instanceof String) {
((HttpEntityEnclosingRequest) httpRequest).setEntity(new StringEntity((String) body, ContentType.APPLICATION_JSON));
} else if (body instanceof Map) {
List<NameValuePair> nameValuePairs = new ArrayList<>();
((Map<String, String>) body).forEach((key, value) -> {
nameValuePairs.add(new BasicNameValuePair(key, value));
});
if (httpRequest instanceof HttpPost) {
((HttpPost) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else if (httpRequest instanceof HttpPut) {
((HttpPut) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else if (httpRequest instanceof HttpPatch) {
((HttpPatch) httpRequest).setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else {
throw new RuntimeException("不支持的 httpRequest: "+httpRequest.getMethod());
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
httpRequest.setConfig(RequestConfig.custom().setConnectTimeout(5000).setSocketTimeout(5000).build());
final HttpRequestBase httpRequestBase = httpRequest;
if (Objects.nonNull(headers)) {
headers.forEach((key, value) -> httpRequestBase.addHeader(key, value));
}
return httpRequest;
}
/**
* 創建一個允許訪問自簽名站點的 http 客戶端
*
* @return 一個允許訪問自簽名站點的 http 客戶端
*/
public static CloseableHttpClient createAcceptSelfSignedCertificateClient() {
try {
// use the TrustSelfSignedStrategy to allow Self Signed Certificates
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
// we can optionally disable hostname verification.
// if you don't want to further weaken the security, you don't have to include this.
HostnameVerifier allowAllHosts = new NoopHostnameVerifier();
// create an SSL Socket Factory to use the SSLContext with the trust self signed certificate strategy
// and allow all hosts verifier.
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts);
// finally create the HttpClient using HttpClient factory methods and assign the ssl socket factory
return HttpClients
.custom()
.setSSLSocketFactory(connectionFactory)
.build();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
public static HttpResponse getResponseWithLine(CloseableHttpClient httpClient, int method, HttpRequestBase httpRequest) {
CloseableHttpResponse httpResponse = null;
String body = null;
try {
httpResponse = httpClient.execute(httpRequest);
if (method != HTTP_METHOD_HEAD) {
HttpEntity httpEntity = httpResponse.getEntity();
if (Objects.nonNull(httpEntity)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpEntity.getContent(), "utf-8"));
String str = null;
StringBuffer stringBuffer = new StringBuffer();
while (Objects.nonNull(str = reader.readLine())) {
if (StringUtils.isBlank(str)) {
continue;
}
str = str + "\r\n";
stringBuffer.append(str);
}
body = stringBuffer.toString();
}
}
} catch (Exception e) {
e.printStackTrace();
httpResponse = null;
} finally {
try {
httpResponse.close();
} catch (Exception ex) {
httpResponse = null;
}
try {
httpClient.close();
} catch (Exception ex) {
httpResponse = null;
}
}
if (Objects.isNull(httpResponse)) {
return null;
}
HttpResponse response = new HttpResponse();
response.response = httpResponse;
response.body = body;
return response;
}
}
java工具類在使用的時候儘量不要使用靜態引入 + 星號的方法,一旦用多了,可能就不知道某個屬性、常量或者變量方法究竟是哪個工具類裏面的了。
然後利用此網絡工具類構建出網絡請求就好了,具體的方案有很多。最後我給大家看看效果。
我們在封裝的時候,將整個(也可分開,看架構)包裝模塊定義到一個服務中,在需要使用的地方直接注入該服務就行了
下面的代碼中的業務模塊是註冊邏輯的一部分,用到了兩個服務。
你能看出那個服務是遠程服務嗎?看不出來?遠程過程調用達到的就是這麼個效果,減少調用代碼對業務代碼的侵入性。