我们平时开发项目的时候,经常会需要远程调用下其他服务提供的接口,于是我们会使用一些 HTTP 工具类比如 Hutool 提供的 HttpUtil。SpringBoot 3.0 出了一个
Http Interface
的新特性,它允许我们使用声明式服务调用的方式来调用远程接口,今天我们就来聊聊它的使用!
简介
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/java/technologies/downloads/
-
由于
Http Interface
需要依赖 webflux 来实现,我们还需添加它的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
基本使用
下面,我们来体验下
Http Interface
的基本使用。
-
首先我们准备一个服务来方便远程调用,打开 Swagger 看下,里面有一个登录接口和需要登录认证的商品品牌 CRUD 接口
-
先在
application.yml
中配置好服务地址;
remote:baseUrl: http://localhost:8088/
-
再通过
@HttpExchange
声明一个 Http 服务,使用@PostExchange
注解表示进行 POST 请求;
/*** 定义Http接口,用于调用远程的UmsAdmin服务* Created by macro on 2022/1/19.*/
@HttpExchange
public interface UmsAdminApi {@PostExchange("admin/login")CommonResult<LoginInfo> login(@RequestParam("username") String username, @RequestParam("password") String password);
}
-
再创建一个远程调用品牌服务的接口,参数注解使用我们平时写Controller 方法用的那些即可;
/*** 定义Http接口,用于调用远程的PmsBrand服务* Created by macro on 2022/1/19.*/
@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 中;
/*** 登录token存储(在Session中)* Created by macro on 2022/1/19.*/
@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;@Autowiredprivate TokenHolder tokenHolder;@BeanWebClient 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();}@BeanUmsAdminApi umsAdminApi(WebClient client) {HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();return factory.createClient(UmsAdminApi.class);}@BeanPmsBrandApi pmsBrandApi(WebClient client) {HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();return factory.createClient(PmsBrandApi.class);}
}
-
接下来在 Controller 中注入 Http 服务对象,然后进行调用即可;
/*** HttpInterface测试接口* Created by macro on 2022/1/19.*/
@RestController
@Api(tags = "HttpInterfaceController")
@Tag(name = "HttpInterfaceController", description = "HttpInterface测试接口")
@RequestMapping("/remote")
public class HttpInterfaceController {@Autowiredprivate UmsAdminApi umsAdminApi;@Autowiredprivate PmsBrandApi pmsBrandApi;@Autowiredprivate 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 时会造成一定的麻烦,如果能独立出来就更好了!