WeClient,一个简单的声明式Rest接口调用组件

1、背景

SpringBoot项目中,经常会使用RestTemplate调用内部微服务或第三方服务接口,需要调用RestTemplate对象的api完成功能。
每次写这些接口调用的测试用例时,还需要模拟RestTemplate对象api的调用结果,总感觉比较繁琐。
对于开发而言,只需要关注到微服务或三方服务的接口入参和出参即可,调用的过程不应该分散过多的关注。

于是参考OpenFeign,写了一个简单的声明式Rest调用组件,让RestTemplate变得透明,让开发人员可以更专注与直接与业务相关的功能逻辑开发。

声明式Rest接口调用指的是使用声明式的方式调用Restful API接口。它是一种更加简单、高效和优雅的调用方式,避免了手动构建请求、解析响应等繁琐的工作。使用声明式Rest接口调用,开发人员只需要定义接口的路径、请求参数和响应数据的格式,然后由框架或工具自动完成请求和响应的处理,从而提高了开发效率和代码的可读性。一些常见的框架或工具,如Feign、Retrofit、RestTemplate等,都支持声明式Rest接口调用。

2、WeClient组件简介

一个简单的声明式Rest调用组件,底层基于RestTemplate,可以实现对普通的GET、POST等请求的调用,支持cookie、header等入参,暂不支持文件上传以及SPEL表达式。
开启组件后,将其它微服务或三方服务声明为本地接口,就可以像调用本地接口一样调用其它微服务或三方服务。
WeClient组件主要包括了两个注解@EnableWeClients和@WeClient。

2.1、@EnableWeClients

@EnableWeClients注解用于开启WeClient组件。一般在SpringBoot启动类上注解即可。
除了缺省的value属性外,其主要属性有两个,一个是basePackages,一个是clients。

2.1.1、basePackages

basePackages是value的属性别名。用于配置微服务或三方服务的接口的位置即包路径。
WeClient组件将会扫描包路径下的所有注解了@WeClient的接口,为这些接口生成代理,放入Spring容器中托管。
如果同时配置了clients,则basePackages属性将失效,clients属性优先。

@EnableWeClients(basePackages = {"com.example.demo.rpc"})

@EnableWeClients("com.example.demo.rpc")

2.1.2、clients

clients属性用于配置微服务或三方服务的接口类列表。
WeClient组件将会扫描这些注解了@WeClient的接口,为这些接口生成代理,放入Spring容器中托管。
如果同时配置了basePackages,则basePackages属性将失效,clients属性优先。

@EnableWeClients(clients = {com.example.demo.rpc.TenantUserClient.class})

2.2、@WeClient

@WeClient用于标识其它微服务或三方服务的接口,WeClient将会代理这些接口的调用。

2.2.1、serviceId

微服务id,用于查找对应微服务的请求地址。一般的小项目,我们可以把微服务id和请求地址的映射保存在yaml配置文件中,这是最简单的实现。
查找到的微服务请求地址,会与contextPath属性、basePath属性,以及接口具体方法(RequestMapping注解及其派生注解)的path属性进行拼接。
serivceId与service有且只能配置其中1个。

2.2.2、service

第三方服务请求地址,一般设置为https|http://主机:端口号格式。支持从环境变量即配置文件中读取,比如配置为${third-service-url}。
service将和contextPath属性、basePath属性,以及接口具体方法(RequestMapping注解及其派生注解)的path属性进行拼接。
serivceId与service有且只能配置其中1个。

2.2.3、contextPath

用于配置微服务或第三放服务的上下文路径,可选属性。比如/api/v1

2.2.4、basePath

用于配置请求的基础路径,比如实体或模块等,可选属性。比如/user

2.2.5、primary

是否标识为主要的bean. 缺省为true即可。

2.2.6、restTemplateName

指定Spring容器中托管的restTemplate的bean名称,默认为okRestTemplate。

3、代码示例

3.1、启动WeClient组件

@SpringBootApplication
@EnableWebMvc
@Slf4j
// 其它微服务或第三方服务的接口均定义在com.example.demo.rpc包中
@EnableWeClients(basePackages = "com.example.demo.rpc")
public class DemoApplication {
	/**
	 * APP启动入口
	 *
	 * @param args 参数
	 */
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
		log.info("DemoApplication Started Successfully.");
	}
}

3.2、配置微服务接口

3.2.1 如何内部微服务接口

假设项目中有一个用户服务(微服务),提供了增删改查的接口。在com.example.demo.rpc包下创建InnerUserService接口。

/**
 * 内部微服务:用户服务
 *
 * @author Bruce.CH
 * @since 2023年08月27日
 */
@WeClient(serviceId = "services.user-service", contextPath = "/api/v1", basePath = "/user")
public interface InnerUserService {
    /**
     * 创建用户
     *
     * @param loginToken loginToken
     * @param accessToken accessToken
     * @param language language
     * @param tenantId tenantId
     * @param commitId commitId
     * @param locale locale
     * @param user user
     * @return 创建成功返回用户id,否则为-1
     */
    @PostMapping("/create/{commitId}")
    Result<Long> create(@CookieValue("Login-Token") String loginToken,
                        @CookieValue(value = "Access-Token") String accessToken,
                        @RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") String language,
                        @RequestHeader(value = "Tenant-Id") String tenantId,
                        @PathVariable("commitId") Long commitId,
                        @RequestParam(value = "locale", defaultValue = "zh-CN") String locale,
                        @RequestBody UserParam user);
}

用户服务的请求地址可能在开发环境、测试环境、生产环境是不一样的,需要application.yml配置文件中配置微服务的请求地址

services:
  user-service: https://127.0.0.1:8080

3.2.2、如何配置第三方服务接口

假设项目中有需要调用一个第三方的微服务创建租户。在com.example.demo.rpc包下创建ThirdPartyService接口。

/**
 * 第三方服务接口:租户服务
 *
 * @author Bruce.CH
 * @since 2023年08月27日
 */
@WeClient(service = "https://1.1.1.1:8081", contextPath = "/api")
public interface ThirdPartyService {
    /**
     * 创建租户:V1版本接口
     *
     * @param loginToken loginToken
     * @param accessToken accessToken
     * @param language language
     * @param commitId commitId
     * @param locale locale
     * @param param param
     * @return 创建成功返回租户id,否则返回-1
     */
    @PostMapping("/v1/tenant/create/{commitId}")
    Result<Long> create(@CookieValue("Login-Token") String loginToken,
                        @CookieValue(value = "Access-Token") String accessToken,
                        @RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") String language,
                        @PathVariable("commitId") Long commitId,
                        @RequestParam(value = "locale", defaultValue = "zh-CN") String locale,
                        @RequestBody TenantCreateParam param);

    /**
     * 创建租户:V2版本接口
     *
     * @param commitId commitId
     * @param param param
     * @return 创建成功返回租户id,否则返回-1
     */
    @PostMapping("/v2/create/{commitId}")
    Result<Long> createV2(@PathVariable("commitId") Long commitId, @RequestBody TenantCreateParam param);
}

其中service也可以在yaml文件中配置指定,比如配置为${services.third-party-service},对应在application.yml文件增加如下配置:

services:
  third-party-service: https://1.1.1.1:8081

3.3、调用服务

此时就可以像调用本地普通的接口一样调用其它微服务或三方服务的接口:

@Service
public class MyService {
    // 省略...

    // 注入内部微服务
    @Resource
    private InnerUserService innerUserService;

    // 注入第三方服务
    @Resource
    private ThirdPartyService thirdPartyService;

    /**
     * 测试
     */
    public void test() {
        // 调用内部微服务的用户服务创建用户
        Result<Long> createUserResult = innerUserService.create("loginToken", "accessToken", "zh-CN",
                "tenantId", 1L, "zh", new UserParam());

        // 调用第三方服务创建租户
        Result<Long> createTenantResult = thirdPartyService.createV2(2L, new TenantCreateParam());
    }
}

WeClient源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成尚谦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值