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

macrozheng發表於2022-12-14
我們平時開發專案的時候,經常會需要遠端呼叫下其他服務提供的介面,於是我們會使用一些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
<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/jav...

  • 由於Http Interface需要依賴webflux來實現,我們還需新增它的依賴。
<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...

  • 先在application.yml中配置好mall-tiny-swagger的服務地址;
remote:
  baseUrl: http://localhost:8088/
  • 再透過@HttpExchange宣告一個Http服務,使用@PostExchange註解表示進行POST請求;
/**
 * @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方法用的那些即可;
/**
 * @auther macrozheng
 * @description 定義Http介面,用於呼叫遠端的PmsBrand服務
 * @date 2022/1/19
 * @github https://github.com/macrozheng
 */
@HttpExchange
public interface PmsBrandApi {
    @GetExchange("brand/list")
    CommonResult<CommonPage<PmsBrand>> 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中;
/**
 * @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服務物件即可,由於品牌服務需要新增認證頭才能正常訪問,所以使用了過濾器進行統一新增;
@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服務物件,然後進行呼叫即可;
/**
 * @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...

專案原始碼地址

https://github.com/macrozheng...

相關文章