還在用HttpUtil?SpringBoot 3.0全新HTTP客户端工具來了,用起來夠優雅!

語言: CN / TW / HK

我們平時開發項目的時候,經常會需要遠程調用下其他服務提供的接口,於是我們會使用一些HTTP工具類比如Hutool提供的HttpUtil。前不久SpringBoot 3.0發佈了,出了一個Http Interface的新特性,它允許我們使用聲明式服務調用的方式來調用遠程接口,今天我們就來聊聊它的使用!

SpringBoot實戰電商項目mall(50k+star)地址:https://github.com/macrozheng/mall

簡介

Http Interface讓你可以像定義Java接口那樣定義HTTP服務,而且用法和你平時寫Controller中方法完全一致。它會為這些HTTP服務接口自動生成代理實現類,底層是基於Webflux的WebClient實現的。

使用聲明式服務調用確實夠優雅,下面是一段使用Http Interface聲明的Http服務代碼。

使用

在SpringBoot 3.0中使用Http Interface是非常簡單的,下面我們就來體驗下。

依賴集成

  • 首先在項目的pom.xml中定義好SpringBoot的版本為3.0.0

xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> <relativePath/> <!-- lookup parent from repository --> </parent>

  • 由於SpringBoot最低要求為Java 17,我們需要先安裝好JDK 17,安裝完成後配置項目的SDK版本為Java 17,JDK下載地址:https://www.oracle.com/cn/java/technologies/downloads/

  • 由於Http Interface需要依賴webflux來實現,我們還需添加它的依賴。

xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>

基本使用

下面以調用mall-tiny-swagger中的接口為例,我們來體驗下Http Interface的基本使用。

  • 首先我們準備一個服務來方便遠程調用,使用的是之前的mall-tiny-swagger這個Demo,打開Swagger看下,裏面有一個登錄接口和需要登錄認證的商品品牌CRUD接口,項目地址:https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-swagger

  • 先在application.yml中配置好mall-tiny-swagger的服務地址;

yaml remote: baseUrl: http://localhost:8088/

  • 再通過@HttpExchange聲明一個Http服務,使用@PostExchange註解表示進行POST請求;

```java /* * @auther macrozheng * @description 定義Http接口,用於調用遠程的UmsAdmin服務 * @date 2022/1/19 * @github https://github.com/macrozheng / @HttpExchange public interface UmsAdminApi {

@PostExchange("admin/login")
CommonResult<LoginInfo> login(@RequestParam("username") String username, @RequestParam("password") String password);

} ```

  • 再創建一個遠程調用品牌服務的接口,參數註解使用我們平時寫Controller方法用的那些即可;

```java /* * @auther macrozheng * @description 定義Http接口,用於調用遠程的PmsBrand服務 * @date 2022/1/19 * @github https://github.com/macrozheng / @HttpExchange public interface PmsBrandApi { @GetExchange("brand/list") CommonResult> list(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize);

@GetExchange("brand/{id}")
CommonResult<PmsBrand> detail(@PathVariable("id") Long id);

@PostExchange("brand/create")
CommonResult create(@RequestBody PmsBrand pmsBrand);

@PostExchange("brand/update/{id}")
CommonResult update(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand);

@GetExchange("brand/delete/{id}")
CommonResult delete(@PathVariable("id") Long id);

} ```

  • 為方便後續調用需要登錄認證的接口,我創建了TokenHolder這個類,把token存儲到了Session中;

```java / * @auther macrozheng * @description 登錄token存儲(在Session中) * @date 2022/1/19 * @github https://github.com/macrozheng */ @Component public class TokenHolder { / * 添加token */ public void putToken(String token) { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest(); request.getSession().setAttribute("token", token); }

/**
 * 獲取token
 */
public String getToken() {
    RequestAttributes ra = RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
    Object token = request.getSession().getAttribute("token");
    if(token!=null){
        return (String) token;
    }
    return null;
}

} ```

  • 創建Java配置,配置好請求用的客户端WebClient及Http服務對象即可,由於品牌服務需要添加認證頭才能正常訪問,所以使用了過濾器進行統一添加;

```java @Configuration public class HttpInterfaceConfig {

@Value("${remote.baseUrl}")
private String baseUrl;
@Autowired
private TokenHolder tokenHolder;

@Bean
WebClient webClient() {
    return WebClient.builder()
            //添加全局默認請求頭
            .defaultHeader("source", "http-interface")
            //給請求添加過濾器,添加自定義的認證頭
            .filter((request, next) -> {
                ClientRequest filtered = ClientRequest.from(request)
                        .header("Authorization", tokenHolder.getToken())
                        .build();
                return next.exchange(filtered);
            })
            .baseUrl(baseUrl).build();
}

@Bean
UmsAdminApi umsAdminApi(WebClient client) {
    HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
    return factory.createClient(UmsAdminApi.class);
}

@Bean
PmsBrandApi pmsBrandApi(WebClient client) {
    HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
    return factory.createClient(PmsBrandApi.class);
}

} ```

  • 接下來在Controller中注入Http服務對象,然後進行調用即可;

```java /* * @auther macrozheng * @description HttpInterface測試接口 * @date 2022/1/19 * @github https://github.com/macrozheng / @RestController @Api(tags = "HttpInterfaceController") @Tag(name = "HttpInterfaceController", description = "HttpInterface測試接口") @RequestMapping("/remote") public class HttpInterfaceController {

@Autowired
private UmsAdminApi umsAdminApi;
@Autowired
private PmsBrandApi pmsBrandApi;
@Autowired
private TokenHolder tokenHolder;

@ApiOperation(value = "調用遠程登錄接口獲取token")
@PostMapping(value = "/admin/login")
public CommonResult<LoginInfo> login(@RequestParam String username, @RequestParam String password) {
    CommonResult<LoginInfo> result = umsAdminApi.login(username, password);
    LoginInfo loginInfo = result.getData();
    if (result.getData() != null) {
        tokenHolder.putToken(loginInfo.getTokenHead() + " " + loginInfo.getToken());
    }
    return result;
}

@ApiOperation("調用遠程接口分頁查詢品牌列表")
@GetMapping(value = "/brand/list")
public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                    @ApiParam("頁碼") Integer pageNum,
                                                    @RequestParam(value = "pageSize", defaultValue = "3")
                                                    @ApiParam("每頁數量") Integer pageSize) {
    return pmsBrandApi.list(pageNum, pageSize);
}

@ApiOperation("調用遠程接口獲取指定id的品牌詳情")
@GetMapping(value = "/brand/{id}")
public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
    return pmsBrandApi.detail(id);
}

@ApiOperation("調用遠程接口添加品牌")
@PostMapping(value = "/brand/create")
public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
    return pmsBrandApi.create(pmsBrand);
}

@ApiOperation("調用遠程接口更新指定id品牌信息")
@PostMapping(value = "/brand/update/{id}")
public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrand) {
    return pmsBrandApi.update(id,pmsBrand);
}

@ApiOperation("調用遠程接口刪除指定id的品牌")
@GetMapping(value = "/delete/{id}")
public CommonResult deleteBrand(@PathVariable("id") Long id) {
    return  pmsBrandApi.delete(id);
}

} ```

測試

  • 下面我們通過Postman進行測試,首先調用登錄接口獲取到遠程服務返回的token了;

  • 再調用下需要登錄認證的品牌列表接口,發現可以正常訪問。

總結

Http Interface讓我們只需定義接口,無需定義方法實現就能進行遠程HTTP調用,確實非常方便!但是其實現依賴Webflux的WebClient,在我們使用SpringMVC時會造成一定的麻煩,如果能獨立出來就更好了!

參考資料

官方文檔:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html

項目源碼地址

https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-http-interface

「其他文章」