在專案開發中,系統間會存在介面呼叫,在微服務架構中可以使用 Dubbo 來實現服務間的 RPC 呼叫,也可以使用 Spring Could 完成 API 呼叫,這些框架遮蔽了 API 呼叫中的像服務建連、通訊協議、序列化等等細節,讓開發者可以很輕鬆發起一個介面呼叫,但是當需要呼叫一個外部系統時,由於通訊協議、技術框架、網路隔離、安全性等因素,一般會採用原生 HTTP 協議進行介面呼叫
URLConnection在 Java 中,JDK 提供了 HTTP 的工具類 URLConnection,透過 URLConnection 可以發起 HTTP 請求,如:
HttpURLConnection connection = null;InputStream inputStream = null;InputStreamReader inputStreamReader = null;BufferedReader bufferedReader = null;try { connection = (HttpURLConnection)(new URL("http://www.api.com/get")).openConnection(); connection.connect(); // 讀取介面返回內容 inputStream = connection.getInputStream(); inputStreamReader = new InputStreamReader(inputStream); bufferedReader = new BufferedReader(inputStreamReader); String line = bufferedReader.readLine(); StringBuffer lines = new StringBuffer(); while(line != null) { line = bufferedReader.readLine(); lines.append(line); } // 反序列化成 json JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity())); // TODO // jsonObject.get('xx') // …… } catch(Exception e) { e.printStackTrace();} finally { connection.disconnect(); if(inputStream != null) inputStream.close(); if(inputStreamReader != null) inputStreamReader.close(); if(bufferedReader != null) bufferedReader.close();}
在不借助任何框架的前提下使用 JDK 自帶的類庫來發起 HTTP 呼叫
HttpClient在實際專案中一般不會使用 JDK 自帶的 URLConnection,原因是效能不高,沒有連線池,程式碼也很臃腫,所以一般會使用 HttpClient 來代替 URLConnection,這也是大家比較常見的使用方式,如:
SchemeRegistry schreg = new SchemeRegistry();schreg.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager(schreg);conMgr.setMaxTotal(200);// 連線池最大數conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());// 發起 API 呼叫DefaultHttpClient defaultHttpClient = new DefaultHttpClient(conMgr);HttpGet get = new HttpGet("http://www.api.com/get");CloseableHttpResponse response = defaultHttpClient.execute(get);// 反序列化成 jsonJSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));// TODO// jsonObject.get('xx')// ……EntityUtils.consume(response.getEntity());
一般來說 HttpClient 可以滿足日常的開發了,如果是使用 json 作為序列化協議的話,則需要在呼叫介面和獲取介面返回值時分別進行序列化和反序列化操作,這樣看似沒有問題,但假如當系統中需要呼叫100個不同的 HTTP 介面,每個介面都需要處理序列化、反序列化以及上面的 HttpClient 相關的程式碼,程式碼可讀性就非常差,系統的可維護性就低,當然你可以封裝 HttpClient 的相關方法,對序列化、反序列化進行泛化封裝,提高可讀性,此外也有另一種更好的處理方法,使用 Feign 框架,像呼叫本地方法一樣呼叫 API。
Feign引入依賴
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> <version>10.10.1</version></dependency>
使用 Feign 呼叫介面跟呼叫 RPC 一樣,都是先定義 API 的介面
interface API { @RequestLine("GET /get") @Headers("Content-Type: application/json") Response get(Request request);}
API 介面聲明瞭一個 get 方法,介面的地址為 /get ,同時在 header 頭中加入了 Content-Type,Request 作為引數是 get 介面的引數,Response 作為返回值是 get 介面的返回值,Feign 會自動將 Request 物件序列化成 /get 的引數,同時把介面返回值序列化成 Response 物件。
定義好介面後,就可以透過 Feign 來生成 "http://www.api.com" 介面的代理類
API api = Feign.builder() .decoder(new JacksonDecoder()) .encoder(new JacksonEncoder()) .client(new ApacheHttpClient(HttpClientBuilder.create().setMaxConnPerRoute(100).setMaxConnTotal(500).build())) .logger(new Slf4jLogger()) .addCapability(new Metrics5Capability()) .retryer(Retryer.NEVER_RETRY) .target(Oudianyun.class, "http://www.api.com");
透過 Feign.builder() 為每個域名生成一個 API 代理類,其中序列化協議使用了 json;HTTP 框架使用了 Apache HttpClient,在 Feign 內部最終還是透過 HttpClient 來發送 HTTP 請求,也可以只用 OkHttp;日誌使用了 Slf4j
透過 Feign.builder 得到 API 的代理類後,就可以發起介面呼叫了
// 構建引數Request request = new Request();request.setId("abc");// 呼叫 get 介面Response response = api.get(request);
發現沒有,使用 Feign 後根據不用關心 HTTP 請求中需要處理的序列化、請求頭等硬編碼,這些都是 Feign 幫我們實現了,使用時只需要根據不同的域名透過 Feign 來生成代理類,然後跟呼叫本地方法一樣直接呼叫介面
上面完成簡單的 Feign 的使用,在實際開發中這還是遠不夠的,上面的 API 物件是透過硬編碼的方式來建立的,而實際專案中物件一般都是交給 Spring 來管理的,因此最佳的方式是把 Feign 和 Spring 整合
Spring Cloud FeignSpring Cloud 整合了 Feign,並對其進行的改造並將整合內容放到了 spring-cloud-openfeign 模組中,相比原生的 Feign 來說 Spring 容器管理了所有的物件,並對其進行了增強,加入了 fallback機制、負載均衡等特性。
引入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.6.RELEASE</version></dependency>
跟 Feign 一樣,Spring Cloud Feign 也是先定義 API 的介面
@FeignClient(value = "SpringCloudAPI", url ="http://www.api.com", fallback = SpringCloudAPIFallback.class)interface SpringCloudAPI { @GetMapping("/get") Response get(Request request); }
使用 @FeignClient 註解來宣告 url 以及 fallback 降級處理類,當接口出現異常時會呼叫 SpringCloudAPIFallback 類中的降級方法,降級類如下:
@Componentclass SpringCloudAPIFallback implements SpringCloudAPI{ public Response get(Request request) { return "接口出現異常,使用 mock 資料"; }}
透過在 Spring Boot 中新增 @EnableFeignClients 開啟 Spring Cloud Feign 功能
@SpringBootApplication(scanBasePackages = "feign")@EnableFeignClients(basePackages = "feign")public class SpringCloudFeignDemo { public static void main(String[] args) { new SpringApplication(SpringCloudFeignDemo.class).run(args); }}
Spring Boot 中啟用 Feign HttpClient 的引數配置如下:
feign.httpclient.enabled=truefeign.httpclient.connection-timeout=2000feign.httpclient.connection-timer-repeat=3000feign.httpclient.max-connections=500feign.httpclient.max-connections-per-route=500feign.httpclient.time-to-live=900feign.httpclient.time-to-live-unit=SECONDS
Spring Could Feign 的 Fallback 是依賴 hystrix 來實現降級的,由於 hystrix 已經不維護了,因此推薦使用 Alibaba 的 Sentinel 來代替 hystrix,加入以下配置啟用 Sentinel
引入依賴
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.3.RELEASE</version></dependency>
開啟配置
feign.sentinel.enabled=true