前言
我们日常在开发项目的过程中,经常会遇到需要远程调用其他服务接口的情况,这时我们会借助一些HTTP工具类,比如Hutool提供的HttpUtil。近期,SpringBoot 3.0的发布带来了一个名为Http Interface的新特性,它允许我们采用声明式服务调用的方式来便捷地调用远程接口,接下来我们就深入探讨一下它的具体用法!
简介
Http Interface特性使得你能够像定义Java接口那样来定义HTTP服务,并且其使用方法与你平时在Controller中编写方法的方式如出一辙。这一特性会自动为这些HTTP服务接口生成代理实现类,而其底层则是依托于Webflux的WebClient来实现的。
示例代码
在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>
首先需要先安装好JDK 17,安装完成后配置项目的SDK版本为Java 17
由于Http Interface特性依赖于webflux来实现,因此我们还需要为其添加相应的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
以调用mall-tiny-swagger中的接口为实例,我们来亲身体验一下Http Interface的基本操作流程。
先在application.yml
配置文件中设置好mall-tiny-swagger的服务地址;
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方法时所使用的那些注解即可;
/**
* @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);
}
}
总结
Http Interface提供了一种便捷的方式,让我们只需定义接口而无需具体实现方法,就能轻松进行远程HTTP调用,这无疑极大地提高了开发效率。然而,其实现依赖于Webflux的WebClient,这在采用SpringMVC框架的项目中可能会带来一些兼容性和集成的挑战。如果能够将这一功能从Webflux中独立出来,使其能够更灵活地与不同框架(包括SpringMVC)集成,那将更加理想。