前面我們介紹了通過springcloud的eureka服務(wù)注冊(cè)組件實(shí)現(xiàn),并且實(shí)現(xiàn)了多機(jī)互備的HA,同時(shí)也將之前寫的springboot的服務(wù)注冊(cè)到了eureka上,今天我們主要來介紹下作為服務(wù)使用者如何去使用這些服務(wù)接口并且實(shí)現(xiàn)基于服務(wù)化的軟負(fù)載均衡。對(duì)于之前實(shí)現(xiàn)的springboot的rest接口服務(wù),一般來說進(jìn)行restful接口的接收和拆組包,可以通過apache的httpclient、jdk的URLConnection、okhttp等http請(qǐng)求庫,也可以通過spring提供的resttemplate,這里我們使用springcloud推薦的feign來進(jìn)行報(bào)文解析來體會(huì)下它的優(yōu)勢(shì),為什么spring會(huì)大力推薦一個(gè)新的rest請(qǐng)求組件,比傳統(tǒng)的使用httpclient等有什么優(yōu)勢(shì)。
1. POM增加Feign和Ribbon相關(guān)依賴庫
新建一個(gè)springboot工程,并添加cloud的依賴,本文中使用的是1.4.7版本,具體的代碼可以參見文后的源碼,這里需要在pom中添加對(duì)ribbon和feign的依賴。還需要加入對(duì)Zuul網(wǎng)關(guān)組件的依賴,如果沒有網(wǎng)關(guān)組件,在啟動(dòng)的時(shí)候會(huì)報(bào)Hystrix的錯(cuò)誤,“Caused by: java.lang.ClassNotFoundException: com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect?!?/p>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2. Application啟動(dòng)類增加Feign依賴
在application類中需要增加在類名前面增加@EnableFeignClients的注解,這個(gè)注解的意思就是在該應(yīng)用被啟動(dòng)的時(shí)候,會(huì)去所有類中搜索定義為FeignClient的接口,并自動(dòng)注冊(cè)到spring ioc容器中,然后會(huì)對(duì)所有使用client實(shí)現(xiàn)類的對(duì)象進(jìn)行自動(dòng)依賴注入。這里還有EurekaClient的注解是因?yàn)楹竺娑xFeignClient的時(shí)候需要定義接口的服務(wù)名而不是ip地址,需要定義EurekaClient才能發(fā)現(xiàn)相關(guān)服務(wù),并且實(shí)現(xiàn)軟負(fù)載。
@SpringCloudApplication
@EnableFeignClients
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 創(chuàng)建Feign Client接口
下面來到最重要的一步,就是定義Feign的Client接口,這里Client接口其實(shí)就是對(duì)提供服務(wù)的restful接口的本地定義,定義好后,在其他類中就可以像使用RPC本地接口類一樣使用遠(yuǎn)程服務(wù)接口了,想想我們用httpclient、okhttp是怎么實(shí)現(xiàn)報(bào)文接收的功能的,首先我們需要定義httpclient對(duì)象,按照服務(wù)端的接口要求組合json報(bào)文,然后定義是get還是post,不同的傳輸方法還需要調(diào)用不同的方法(doGet、doPost),這才將一個(gè)請(qǐng)求發(fā)出去,然后從inputstream中循環(huán)讀取字節(jié)流或者從Reader中讀取字符流,再將字符流反序列化成對(duì)象,全部需要編碼實(shí)現(xiàn),整個(gè)過程雖然結(jié)構(gòu)很清晰但是很繁瑣,為什么之前RPC很火,就是因?yàn)槭褂肦PC會(huì)讓client端調(diào)用遠(yuǎn)程服務(wù)接口簡單化,就好像使用本地對(duì)象一樣簡單,但是使用RPC的最大的弊端就是每個(gè)client都需要維護(hù)一個(gè)服務(wù)端的client jar包,這個(gè)jar包中其實(shí)就是定義了接口,一旦服務(wù)端接口有變化,服務(wù)使用方就需要更新jar包,這就使得客戶端對(duì)服務(wù)端產(chǎn)生了強(qiáng)依賴,feign就很好的解決了這個(gè)問題,在client和server端沒有依賴的情況下,讓client使用服務(wù)就像使用本地接口一樣簡單,廢話不多少,下面來定義feign client的接口,這里服務(wù)端的接口就使用之前定義的getUser接口。
@FeignClient("usercenter-provider")
public interface UserFeignClient {
//Feign定義服務(wù)提供者接口
@RequestMapping(value = "/getUser", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
String getUser(@RequestBody String data);
}
@FeignClient("usercenter-provider")注解的意思是該接口里定義的方法全部是“usercenter-provider”這個(gè)serviceid提供的方法,注意這里的serviceid需要和eureka server里定義的服務(wù)提供方的名字一致,不然feign是無法找到相關(guān)服務(wù)的,feign內(nèi)部集成了Ribbon所以通過serviceid找到服務(wù)后,會(huì)通過ribbon自動(dòng)實(shí)現(xiàn)服務(wù)訪問的負(fù)載均衡,可以通過定義ribbon的負(fù)載均衡方法來實(shí)現(xiàn)自己想要的,默認(rèn)得是隨機(jī)訪問方法也可以定義成順序、訪問權(quán)重或者自定義負(fù)載算法,這里就使用了默認(rèn)的負(fù)載,默認(rèn)負(fù)載已經(jīng)能夠滿足大部分需求了。在接口的方法前面需要定義該方法訪問路徑和訪問方法和編碼格式等信息,對(duì)于請(qǐng)求參數(shù)也可以通過@requestBody或者@RequestParameter的value方法進(jìn)行設(shè)置,這里就使用和入?yún)⒚膮?shù)設(shè)定。是不是很簡單,這樣就定義好了遠(yuǎn)程服務(wù)端接口。
4. 遠(yuǎn)程服務(wù)接口使用
上面定義好了Feign Client接口,后面使用就很簡單了,這里按照日常項(xiàng)目結(jié)構(gòu)定義了service層和controller,將feign的接口調(diào)用放在了service的實(shí)現(xiàn)類中,在controller類中進(jìn)行service的調(diào)用,這里需要注意一個(gè)問題,之前在測(cè)試的過程中自己遇到了,因?yàn)椴恍⌒膶戝e(cuò)了,犯了一個(gè)很低級(jí)的錯(cuò)誤,找了1個(gè)小時(shí)才找到原因。。因?yàn)閒eign的client是通過@AutoWired啟動(dòng)的時(shí)候自動(dòng)注入的,這就使得如果client的調(diào)用是在service里的時(shí)候需要將service也放在spring的ioc中進(jìn)行托管,不然就會(huì)報(bào)你的Service沒有定義,并且在Controller使用這個(gè)Service的時(shí)候也要通過ioc容器自動(dòng)注入Service實(shí)現(xiàn),不然會(huì)報(bào)client空指針的錯(cuò)誤,如果調(diào)試過程中發(fā)現(xiàn)FeignClient調(diào)用的時(shí)候報(bào)nullpoint,基本就是由于ioc bean托管導(dǎo)致的問題,我就是因?yàn)閷戝e(cuò)了一行代碼導(dǎo)致了這個(gè)問題。
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserFeignClient userFeignClient;
@Override
public String getUSer(String data) {
return "Feign: " + userFeignClient.getUser(data);
}
}
UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/getUser", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public String getUser(@RequestBody String data){
return userService.getUSer(data);
}
}
5. application.yml參數(shù)設(shè)置
在配置中主要是要配置eureka server集群的地址,因?yàn)樾枰x在哪個(gè)eureka集群中找到feign使用的serviceid。
spring:
application:
name: springcloud-feign-ribbon
server:
port: 8080
eureka:
client:
serviceUrl:
defaultZone: http://node1:8761/eureka/,http://node2:8762/eureka/
小結(jié)
最后我們啟動(dòng)eureka server、usercenter-provider的服務(wù),再啟動(dòng)本次寫的應(yīng)用。

下面通過httprequester來發(fā)起請(qǐng)求,地址欄輸入http://node1:8080/getUser,body欄輸入{"name":"feiweiwei"},可以看到返回了Feign {"name":"feiweiwei"}
