聊聊 Http 服務化改造實踐

2024年2月6日 20点热度 0人点赞

在微服務架構體系中遠程 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領取哦~↓↓

例如要實現如下圖的效果:

原生的 Feign 無法實現該效果,我們需要使用 OpenFeign 類庫,兩者之間的對比如下圖所示:

接下來詳細介紹具體實現方法。

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 包,類也已經被替換成我們自定義的類,目標達成。