在微服務架構體系中遠程 RPC 調用主要包括 Dubbo 與 Http 調用兩個大類,由於 Dubbo 擁有服務註冊中心,並且起服務的命名非常規范,使用包名.類名.方法名進行描述。
而 http 調用通常都是使用 httpclient 等相關類庫,這些在使用上並沒有問題,但 API 都是分散在整個工程的各個地方,如果 HTTP 調用也可以使用類似 Dubbo 服務的表示方法,采用聲明式定義就好了。
在開源的世界中隻有想不到,沒有找不到,為了解決 Feign 的聲明式服務化管理,Feign 框架應運而生,本文主要介紹如何使用 Feign 實現 Http 服務聲明化管理與調用。
1.什麼是 Feign
Feign 是一個 http 請求調用的輕量級框架,可以以 Java 接口註解的方式調用 Http 請求。Feign 通過註解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的請求,封裝了 http 調用流程。
2.快速入門實例
2.1 定義客戶端
首先要引入 Feign 的 maven 依賴,如下圖所示:
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-core</artifactId>
<version>8.18.0</version>
</dependency>
2.2 定義服務調用 API(類似 Dubbo API)
服務調用的 API 聲明代碼如下所示:
@FeignClient
public interface HelloControllerApi {
@RequestLine("GET /api/hello?name={name}")
String hello(@Param(value = "name") String name);
}
這裡的要點是使用 @FeignClient 進行聲明。聲明後就可以通過 HelloControllerApi 進行遠程 HTTP 調用,示例代碼如下:
public class HelloControllerApiTest {
private HelloControllerApi service;
@Before
public void before(){
service = Feign.builder()
.options(new Request.Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3))
.target(HelloControllerApi.class, "http://127.0.0.1:8080");
}
@Test
public void hello(){
// 調用http://127.0.0.1:8080/api/hello?name=world 的http接口
System.out.println(service.hello("world"));
}
}
當然需要在調用方的啟動類上增加 @EnableFeignClients(defaultConfiguration = FeignConfiguration.class)註解。
2.3 定義服務端
服務端與 Feign 並無關系,主要按照 API 的方式實現即可,服務端實現代碼如下所示:
@Controller
@RequestMapping(value = "api")
public class HelloController {
@RequestMapping(value = "/hello", method = {RequestMethod.GET})
@ResponseBody
public String list(@RequestParam String name) {
return "Hello " name;
}
}
//啟動類
@SpringBootApplication(scanBasePackages = {"com.vhicool.manager"})
public class ManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class, args);
}
}
3.實現簽名校驗
上述隻是簡單實用 Feign,接下來以實現簽名校驗為例展示 Feign 的擴展機制。
簽名驗證是最常見的安全機制,首先在客戶端定義一個簽名攔截器,用於生成簽名信息,示范代碼如下圖所示:
public class AuthRequestInterceptor implements feign.RequestInterceptor {
private TokenService tokenService;
public AuthRequestInterceptor(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
public void apply(RequestTemplate template) {
template.header("token", tokenService.getToken());
}
}
並且在 Feign 的全局配置文件中創建對應的攔截器,示例代碼如下:
public class FeignConfiguration {
@Bean
public RequestInterceptor authRequestInterceptor(ResourceIdentity resourceIdentity) {
AuthRequestInterceptor authRequestInterceptor = new AuthRequestInterceptor(resourceIdentity);
authRequestInterceptor.setErrorEncodeType(errorEncodeType);
return authRequestInterceptor;
}
}
同時在服務端獲取 token 並對 token 進行校驗,示例代碼如下:
@Component
public class AuthFilter implements Filter {
@Autowired
private TokenService tokeService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String remoteToken = ((HttpServletRequest) servletRequest).getHeader("token");
if(!tokeService.valid(token)) {
//異常處理邏輯
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
4.服務端自動生成 Feign
上面的示例雖然實現了服務接口的聲明式管理,但調用端、客戶端並沒有顯示的約束關系,接下來展示如何使用客戶端、服務端使用繼承方式定義服務調用 API。
粉絲福利, 免費領取C/C 開發學習資料包、技術視頻/項目代碼,1000道大廠面試題,內容包括(C 基礎,網絡編程,數據庫,中間件,後端開發,音視頻開發,Qt開發,遊戲開發,Linux內核等進階學習資料和最佳學習路線)↓↓↓↓有需要的朋友可以進企鵝裙927239107領取哦~↓↓
例如要實現如下圖的效果:
![](https://news.xinpengboligang.com/upload/keji/e50323d84afb3a2091f30b4ac221935d.jpeg)
原生的 Feign 無法實現該效果,我們需要使用 OpenFeign 類庫,兩者之間的對比如下圖所示:
![](https://news.xinpengboligang.com/upload/keji/6e248acac409f40992639a76e1ee2f26.jpeg)
接下來詳細介紹具體實現方法。
4.1 提取公共 API
首先使用一個模塊定義公共 API,需要引入 maven 依賴,代碼示例如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
接下來定義公共的服務接口,客戶端、服務端都需要實現該接口,公共服務端接口定義如下:
public interface IUserController {
@RequestMapping(value = "user/list-all", method = {RequestMethod.GET})
List<String> listAll(@RequestParam String name);
}
4.2 服務端實現公共 API
首先需要添加相應的 maven 依賴,代碼如下:
public interface IUserController {
@RequestMapping(value = "user/list-all", method = {RequestMethod.GET})
List<String> listAll(@RequestParam String name);
}
服務端采用繼承方式實現,具體代碼如下所示:
@Controller
@RequestMapping
public class UserController implements IUserController {
@Override
@ResponseBody
public List<String> listAll(String name) {
ArrayList<String> list = new ArrayList<>();
list.add("達菲");
list.add("olu");
list.add(name);
return list;
}
}
4.3 客戶端實現公共 API
客戶端首先同樣需要增加相應的依賴,具體代碼如下所示:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.vhicool</groupId>
<artifactId>feign-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
客戶端服務調用類需要繼承公共 API:
@FeignClient(value = "user", url = "http://localhost:8080")
public interface UserApi extends IUserController {
}
同時客戶端啟動類需要增加 @EnableFeignClients 註解,具體示例代碼如下所示:
@SpringBootApplication
@EnableFeignClients
public class ManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class, args);
}
}
同樣基於 Springboot 編程方式,可以為 Feign 配置全局參數,具體如下:
@Configuration
public class FeignConfiguration {
/**
* 請求超時時間
* @return
*/
@Bean
public Request.Options options() {
return new Request.Options(2000, 3500);
}
//攔截器等定義
}
接下來客戶端就可以用如下方式進行調用:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private UserApi userApi;
@Test
public void listAll() {
System.out.println(userApi.listAll("餅餅"));
}
}
當前項目編譯的 jar 包,類也已經被替換成我們自定義的類,目標達成。