SpringBoot 3 新HTTP客户端工具让代码简洁又优雅,不用再用 HttpUtil

科技   2024-10-12 09:57   河北  


前言

我们日常在开发项目的过程中,经常会遇到需要远程调用其他服务接口的情况,这时我们会借助一些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)集成,那将更加理想。





Java技术前沿
专注分享Java技术,包括但不限于 SpringBoot,SpringCloud,Docker,消息中间件等。
 最新文章