二十章 服务注册组件搭建采集接口信息

提供应用服务注册的组件,采集 RPC 服务启动时已经配置了标记注解的对象,摘取出类、接口、方法,用于后续注册到网关中心。

设计

20-设计

网关的注册中心维护着网关和RPC接口的信息,用于把RPC接口分配到网关算力上使用。那么前面的章节已经实现了网关算力的自动注册,同样 RPC 接口也需要自动注册,否则都是人工手动维护这个成本还是非常大的。

应用注册组件的目的就是提供给 RPC 接口生产的服务使用,通过 RPC 生产者服务引入 SDK 组件,并使用注解配置的方式作为接口标记。当服务启动的时候,SDK组件会采集这些被标记了注解的接口和方法,把这些信息收集后向网关注册中心注册,而 assist 就可以将其注入通信组件的 Configuration 配置中,后续通信组件对其进行一系列处理后,返回泛化调用的结果。

实现

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.ray.gateway.sdk.annotation;

import java.lang.annotation.*;

/**
* @author Ray
* @date 2023/5/29 15:12
* @description 网关API生产者自定义类注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ApiProducerClazz {

/**
* 接口名称
*/
String interfaceName() default "";

/**
* 接口版本
*/
String interfaceVersion() default "";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cn.ray.gateway.sdk.annotation;

import java.lang.annotation.*;

/**
* @author Ray
* @date 2023/5/29 15:14
* @description 网关API生产者自定义方法注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ApiProducerMethod {

/**
* 方法名称
*/
String methodName() default "";

/**
* 访问路径:/wg/activity/sayHi
*/
String uri() default "";

/**
* 请求类型:GET、POST、PUT、DELETE
*/
String HttpRequestType() default "GET";

/**
* 是否鉴权:1 是;0 否
*/
int auth() default 0;

}

应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package cn.ray.gateway.sdk.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @author Ray
* @date 2023/5/29 15:26
* @description 应用配置
*/
@ConfigurationProperties("api-gateway-sdk")
public class GatewaySDKServiceProperties {

/** 网关注册中心地址 */
private String address;
/** 系统标识 */
private String systemId;
/** 系统名称 */
private String systemName;
/** RPC 注册中心;zookeeper://127.0.0.1:2181*/
private String systemRegistry;

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getSystemId() {
return systemId;
}

public void setSystemId(String systemId) {
this.systemId = systemId;
}

public String getSystemName() {
return systemName;
}

public void setSystemName(String systemName) {
this.systemName = systemName;
}

public String getSystemRegistry() {
return systemRegistry;
}

public void setSystemRegistry(String systemRegistry) {
this.systemRegistry = systemRegistry;
}
}

注册中心一般都是固定的,如果统一系统用多个注册中心,那么相互之间就不太好通信了,系统标识和名称一般是从统一的上线系统中获取,从对应的配置中获取。这里方便测试,也是为了配置的灵活性,增加了从配置文件中读取相应信息的操作。

信息提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package cn.ray.gateway.sdk.application;

import cn.ray.gateway.sdk.annotation.ApiProducerClazz;
import cn.ray.gateway.sdk.annotation.ApiProducerMethod;
import cn.ray.gateway.sdk.config.GatewaySDKServiceProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.Method;

/**
* @author Ray
* @date 2023/5/29 15:35
* @description 应用服务注册
*/
public class GatewaySDKApplication implements BeanPostProcessor {

private Logger logger = LoggerFactory.getLogger(GatewaySDKApplication.class);

private GatewaySDKServiceProperties properties;

public GatewaySDKApplication(GatewaySDKServiceProperties properties) {
this.properties = properties;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ApiProducerClazz apiProducerClazz = bean.getClass().getAnnotation(ApiProducerClazz.class);
if (null == apiProducerClazz) {
return bean;
}
// 1. 系统信息
logger.info("\n应用注册:系统信息 \nsystemId: {} \nsystemName: {} \nsystemType: {} \nsystemRegistry: {}", properties.getSystemId(), properties.getSystemName(), "RPC", properties.getSystemRegistry());
// 2. 接口信息
logger.info("\n应用注册:接口信息 \nsystemId: {} \ninterfaceId: {} \ninterfaceName: {} \ninterfaceVersion: {}", properties.getSystemId(), bean.getClass().getName(), apiProducerClazz.interfaceName(), apiProducerClazz.interfaceVersion());
// 3. 方法信息
Method[] methods = bean.getClass().getMethods();
for (Method method : methods) {
ApiProducerMethod apiProducerMethod = method.getAnnotation(ApiProducerMethod.class);
if (null == apiProducerMethod) {
continue;
}
// 解析参数
Class<?>[] parameterTypes = method.getParameterTypes();
StringBuilder parameters = new StringBuilder();
for (Class<?> clazz : parameterTypes) {
parameters.append(clazz.getName()).append(",");
}
String parameterType = parameters.toString().substring(0,parameters.toString().lastIndexOf(","));
logger.info("\n应用注册:方法信息 \nsystemId: {} \ninterfaceId: {} \nmethodId: {} \nmethodName: {} \nparameterType: {} \nuri: {} \nhttpCommandType: {} \nauth: {}",
properties.getSystemId(),
bean.getClass().getName(),
method.getName(),
apiProducerMethod.methodName(),
parameterType,
apiProducerMethod.uri(),
apiProducerMethod.HttpRequestType(),
apiProducerMethod.auth());
}
return bean;
}
}

这里实现 BeanPostProcessor 的 postProcessAfterInitialization 方法,在 Bean 实例化之后对其进行处理,判断是否类带有注解 ApiProducerClazz 以及 方法是否带有注解 ApiProducerMethod,并对其进行解析提取对应的系统、接口和方法信息。这里只做了简单的输出日志验证。

测试

新建 sdk-test 工程,引入 sdk-01 ,同时在 application.yml 中配置对应信息。

用户服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.ray.gateway.sdk.interfaces;

import cn.ray.gateway.sdk.annotation.ApiProducerClazz;
import cn.ray.gateway.sdk.annotation.ApiProducerMethod;
import org.springframework.stereotype.Service;

/**
* @author Ray
* @date 2023/5/29 15:59
* @description 用户服务简单测试
*/
@ApiProducerClazz(interfaceName = "用户服务", interfaceVersion = "1.0.0")
@Service
public class UserService {

@ApiProducerMethod(methodName = "探测", uri = "/wg/user/hi", HttpRequestType = "POST", auth = 1)
public String hi(String str) {
return "hi " + str + " by api-gateway-sdk";
}

}

application.yml

1
2
3
4
5
6
7
8
server:
port: 8004

api-gateway-sdk:
address: http://localhost:80 # 注册中心;从这里获取接口信息以及完成注册网关操作
systemId: api-gateway-sdk-test # 系统id
systemName: 网关SDK测试工程 # 系统名称
systemRegistry: zookeeper://localhost:2181 # RPC注册中心

启动测试

20-测试

在日常使用网关服务时,我们通常也是在本地引用一个网关提供好的 SDK, 引入后简单配置就可以完成接口到网关的注册。如果再有一些其他的需求,则可以到网关的控制后台页面上进行修改。