如何通過Java發送HTTP請求,通俗點講,如何通過Java(模擬瀏覽器)發送HTTP請求。
Java有原生的API可用於發送HTTP請求,即java.net.URL、java.net.URLConnection,這些API很好用、很常用,但不夠簡便;
所以,也流行有許多Java HTTP請求的framework,如,Apache的HttpClient。
httpclient之外RPC 以及隊列的使用看可以說也是越來越廣泛了。
在netty等NIO框架因為需要高效的傳輸所以往往選擇RPC,隊列則用於回調以及設備消息之間的傳遞。
Http這個經久不衰的大佬自然不用多說,簡單,支持廣泛,高度兼容性。
HttpClient
HttpClient相比傳統JDK自帶的URLConnection,增加了易用性和靈活性,使得客戶端發送Http請求變得容易。
HttpClient使用:
使用HttpClient發送請求、接收響應很簡單,一般需要如下幾步即可。
1. 創建HttpClient對象。
2. 創建請求方法的實例,並指定請求URL。如果需要發送GET請求,創建HttpGet對象;如果需要發送POST請求,創建HttpPost對象。
3. 如果需要發送請求參數,可調用HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數;對於HttpPost對象而言,也可調用setEntity(HttpEntity entity)方法來設置請求參數。
4. 調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse。
5. 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容。
6. 釋放連接。無論執行方法是否成功,都必須釋放連接
RestTemplate
RestTemplate本是spring-web項目中的一個REST客戶端,它遵循REST的設計原則,提供簡單的API讓我們可以調用HTTP服務。底層是對httpclient進行了封裝。

ps:建議使用exchange ,其他方法都是對execute進行了封裝,都擁有各自的局限性。
RestTemplate可以用來做負載均衡,RestTemplate本身不具有負載均衡的功能,該類也與Spring Cloud沒有關系,但為何加入@LoadBalanced注解后,一個RestTemplate實例就具有負載均衡的功能呢?
實際上這要得益於RestTemplate的攔截器功能。
下面舉例:基於自己配置的HttpClient,創建一個可負載均衡的RestTemplate
HttpClientProperties
HttpClient的配置信息
@ConfigurationProperties(prefix="spring.httpclient") public class HttpClientProperties { private Integer connectTimeOut = 1000; private Integer socketTimeOut = 1000000; private String agent = "agent"; private Integer maxConnPerRoute = 10; private Integer maxConnTotaol = 50; public Integer getConnectTimeOut() { return connectTimeOut; } public void setConnectTimeOut(Integer connectTimeOut) { this.connectTimeOut = connectTimeOut; } public Integer getSocketTimeOut() { return socketTimeOut; } public void setSocketTimeOut(Integer socketTimeOut) { this.socketTimeOut = socketTimeOut; } public String getAgent() { return agent; } public void setAgent(String agent) { this.agent = agent; } public Integer getMaxConnPerRoute() { return maxConnPerRoute; } public void setMaxConnPerRoute(Integer maxConnPerRoute) { this.maxConnPerRoute = maxConnPerRoute; } public Integer getMaxConnTotaol() { return maxConnTotaol; } public void setMaxConnTotaol(Integer maxConnTotaol) { this.maxConnTotaol = maxConnTotaol; } }
HttpClientAutoConfiguration
根據HttpClient.class是否存在,bean是否存在,自動進行配置
@Configuration @ConditionalOnClass({HttpClient.class}) @EnableConfigurationProperties(HttpClientProperties.class) public class HttpClientAutoConfiguration { private final HttpClientProperties properties; public HttpClientAutoConfiguration(HttpClientProperties properties){ this.properties = properties; } /** * httpclient bean 的定義 * @return */ @Bean @ConditionalOnMissingBean(HttpClient.class) public HttpClient httpClient() { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(properties.getConnectTimeOut()) .setSocketTimeout(properties.getSocketTimeOut()).build();// 構建requestConfig HttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig) .setUserAgent(properties.getAgent()) .setMaxConnPerRoute(properties.getMaxConnPerRoute()) .setMaxConnTotal(properties.getMaxConnTotaol()) .build(); return client; } }
@ConditionalOnClass:該注解的參數對應的類必須存在,否則不解析該注解修飾的配置類;
@ConditionalOnMissingBean:該注解表示,如果存在它修飾的類的bean,則不需要再創建這個bean;可以給該注解傳入參數例如@ConditionOnMissingBean(name = "example"),這個表示如果name為“example”的bean存在,這該注解修飾的代碼塊不執行。
RestAutoConfig
創建負載均衡和直連的RestTemplate
@Configuration public class RestAutoConfig { public static class RestTemplateConfig { @Bean//負載均衡的restTemplate @LoadBalanced //spring 對restTemplate bean進行定制,加入loadbalance攔截器進行ip:port的替換 //"http://user/getusername,就能解析成http://127.0.0.1:8083//getusername RestTemplate lbRestTemplate(HttpClient httpclient) { RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient)); template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8"))); template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5()); return template; } @Bean //直連的restTemplat,這時只能使用http://127.0.0.1:8083//getusername地址,不能解析http://user/getusername RestTemplate directRestTemplate(HttpClient httpclient) { RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient)); template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8"))); template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5()); return template; } // FastJsonHttpMessageConvert4有一個bug,它是默認支持MediaType.ALL,spring在處理MediaType.ALL的時候會識別成字節流,而不是json,這里就對他進行改造和處理 public static class FastJsonHttpMessageConvert5 extends FastJsonHttpMessageConverter4{ static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public FastJsonHttpMessageConvert5(){ setDefaultCharset(DEFAULT_CHARSET); setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,new MediaType("application","*+json"))); } } } }
調用RestTemplate的默認構造函數,RestTemplate對象在底層通過使用java.net包下的實現創建HTTP 請求,可以通過使用ClientHttpRequestFactory指定不同的HTTP請求方式。
ClientHttpRequestFactory接口主要提供了兩種實現方式
- 一種是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)創建底層的Http請求連接。
- 一種方式是使用HttpComponentsClientHttpRequestFactory方式,底層使用HttpClient訪問遠程的Http服務,使用HttpClient可以配置連接池和證書等信息。
GenericRest
既支持直連又支持服務發現的調用
/** * 既支持直連又支持服務發現的調用 * */ @Service public class GenericRest { @Autowired @Qualifier("lbRestTemplate") private RestTemplate lbRestTemplate; @Autowired @Qualifier("directRestTemplate") private RestTemplate directRestTemplate; private static final String directFlag = "direct://"; //返回的泛型用ParameterizedTypeReference<T>來指定 public <T> ResponseEntity<T> post(String url,Object reqBody,ParameterizedTypeReference<T> responseType){ RestTemplate template = getRestTemplate(url); url = url.replace(directFlag, ""); return template.exchange(url, HttpMethod.POST,new HttpEntity(reqBody),responseType); } public <T> ResponseEntity<T> get(String url,ParameterizedTypeReference<T> responseType){ RestTemplate template = getRestTemplate(url); url = url.replace(directFlag, ""); return template.exchange(url, HttpMethod.GET,HttpEntity.EMPTY,responseType); } private RestTemplate getRestTemplate(String url) { if (url.contains(directFlag)) { return directRestTemplate; }else { return lbRestTemplate; } } }
exchange支持‘含參數的類型’(即泛型類)作為返回類型,該特性通過‘ParameterizedTypeReference<T>responseType’描述。
進行服務調用:
public List<User> getUserList(User query) { ResponseEntity<RestResponse<List<User>>> resultEntity = rest.post("http://"+ userServiceName + "/user/getList",query, new ParameterizedTypeReference<RestResponse<List<User>>>() {}); RestResponse<List<User>> restResponse = resultEntity.getBody(); if (restResponse.getCode() == 0) { return restResponse.getResult(); }else { return null; } }
SpringCloud Feign—申明式服務調用
雖然RestTemplate已經可以將請求攔截來實現對依賴服務的接口調用,並對Http請求進行封裝處理,形成一套模板化的調用方法,但是對服務依賴的調用可能不只一處,一個接口都會被多次調用,所以我們會像前面那樣針對各個微服務字形封裝一些客戶端接口調用類來包裝這些依賴服務的調用。
由於RestTemplate的封裝,幾乎每一個調用都是簡單的模板化內容,Feign在此基礎上做了進一步的封裝,由它來幫助我們定義和實現依賴服務接口的定義。
在服務消費者創建服務調用接口,通過@FeignClient注解指定服務名來綁定服務,然后再使用SpringMVC的注解來綁定具體該服務提供的REST接口。
@FeignClient("biz-service-0") public interface UserClient { @RequestMapping(method = RequestMethod.GET, value = "/getuser") public User getuserinfo(); @RequestMapping(method = RequestMethod.GET, value = "/getuser") public String getuserinfostr(); }
在服務消費者的web層進行調用:
@RestController public class UserController { @Autowired UserClient userClient; @RequestMapping(value = "/getuserinfo", method = RequestMethod.GET) public User getuserinfo() { return userClient.getuserinfo(); } @RequestMapping(value = "/getuserinfostr", method = RequestMethod.GET) public String getuserinfostr() { return userClient.getuserinfostr(); }
通過Feign我們只需要定義服務綁定接口,以申明式的方法,優雅而簡單的實現了服務調用。
