如何打通SpringCloud与HSF的调用?

背景

2019年我们经历了一整年的各种迁移,其中包括了一项RPC框架的切换。以前我们用的HSF RPC框架,它是来自于阿里巴巴,经过了多年的双11高并发的洗礼,高性能这块儿毫无疑问没有任何的问题,而且它还同时支持TCPHTTP的方式,唯一不太好的就是它不开源,如果出现问题定位起来确实有一些问题与风险。

所以,我们为了拥抱开源,全部采用SpringCloud,系统与系统之间调用是通过FeignClient的方式来调用的,但是由于底层的部分系统由于时间、人力、历史等原因,无法在短时间内都像我们一样能积极响应。所以就出现了SpringCloud与HSF服务同时存在的情况,为了大家再编码过程中都能像本地调用(TCPFeignClient),所以就写了一个代理工具。

交互图

http://static.cyblogs.com/QQ截图20200406181706.png

如果是上面的方式,我们还是能感受到每次都是通过HttpClient等方式发起一次Http请求,写代码时候的体验不是很好。

http://static.cyblogs.com/QQ截图20200406182159.png

为了解决这个问题,那么我们的任务就是来写一个这个代理封装。

分析功能点

了解一下FeignClient

我们参考一下FeignClient的功能一个解析过程,如图:

http://static.cyblogs.com/14126519-4cc483cb15b9dc6d.png

  • 生成动态代理类
  • 解析出等的MethodHandler
  • 动态生成Request
  • Encoder
  • 拦截器处理
  • 日志处理
  • 重试机制
代理需要考虑什么?

http://static.cyblogs.com/QQ截图20200406193343.png

那我们不用说写那么完善,我们的第一个目标就是实现扫描 → 代理 → 发送请求。

因为HSF的参数与标准的Http方式不太一致,所以在发起Http请求的时候,需要特殊的构造一下报文的格式

1
2
curl -d "ArgsTypes=[\"com.cyblogs..QueryConfigReq\"]&ArgsObjects=[{\"relationCode\":\"ABCD\"}]" 
http://127.0.0.1:8083/com.cyblogs.api.ConfigServiceV2Api:1.0.0/queryNewConfig

代码框架实现

SpringBoot总入口,打开@EnableHsfClients注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableHsfClients(basePackages = "com.cyblogs.client.hsf")
public class App {

public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

这里定义好需要扫描的包,具体的类等

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ HsfClientsRegistrar.class })
public @interface EnableHsfClients {
String[] value() default {};

String[] basePackages() default {};

Class<?>[] clients() default {};

}

利用SpirngImportBeanDefinitionRegistrar来进行自动注入生成Bean。

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class HsfClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registerHsfClient(importingClassMetadata, registry);
}

public void registerHsfClient(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

Set<String> basePackages;

Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableHsfClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(HsfClient.class);
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
} else {
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
}

for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@HsfClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(HsfClient.class.getCanonicalName());
registerHsfClient(registry, annotationMetadata, attributes);
}
}
}
}

protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false) {

@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isIndependent()) {
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(),
HsfClientsRegistrar.this.classLoader);
return !target.isAnnotation();
} catch (Exception ex) {
log.error("Could not load target class: " + beanDefinition.getMetadata().getClassName(),
ex);

}
}
return true;
}
return false;

}
};
}

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableHsfClients.class.getCanonicalName());

Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}

if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}

private void registerHsfClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(HsfClientFactoryBean.class);
String version = resolve((String) attributes.get("version"));
String interfaceName = resolve((String) attributes.get("interfaceName"));
if (interfaceName.length() == 0) {
interfaceName = className;
}
definition.addPropertyValue("url", String.format(FORMAT, getUrl(attributes), interfaceName, version));
definition.addPropertyValue("type", className);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);

String alias = interfaceName + "HsfClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setPrimary(true);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
boolean secure = false;
Object securePlaceHolder = attributes.get("secure");
if (securePlaceHolder instanceof Boolean) {
secure = ((Boolean) securePlaceHolder).booleanValue();
} else {
Boolean.parseBoolean(resolve((String) attributes.get("secure")));
}
String protocol = secure ? "https" : "http";
if (!url.contains("://")) {
url = protocol + "://" + url;
}
if (url.endsWith("/")) {//避免设置的url为'schema:ip:port/'格式
url = url.substring(0, url.length() - 1);
}
try {
new URL(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
return url;
}
}

HsfClientFactoryBean定义

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
@Setter
@Getter
public class HsfClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private ApplicationContext applicationContext;
private Class<?> type;
private String url;
private RestTemplate restTemplate;

@Override
public void afterPropertiesSet() throws Exception {
Assert.hasText(url, "url must be set");
Assert.notNull(type, "type must be set");
if (restTemplate == null) {
restTemplate = new RestTemplate();
restTemplate.getMessageConverters().clear();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));//write application/x-www-form-urlencoded request
restTemplate.getMessageConverters().add(new FastJsonHttpMessageConverter());//read and write application/json
}
}

public Object getObject() throws Exception {
Map<Method, HsfMethodHandler> methodToHandler = new LinkedHashMap<Method, HsfMethodHandler>();
for (Method method : type.getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (isDefaultMethod(method)) {
continue;//TODO 暂时忽略
} else {
methodToHandler.put(method, new HsfMethodHandler(restTemplate, type, method, url));
}
}
InvocationHandler handler = new HsfInvocationHandler(methodToHandler);
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { type }, handler);
}

@Override
public Class<?> getObjectType() {
return type;
}

@Override
public boolean isSingleton() {
return true;
}

private boolean isDefaultMethod(Method method) {
final int SYNTHETIC = 0x00001000;
return ((method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)
&& method.getDeclaringClass().isInterface();
}
}

代理类的实现

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
public class HsfInvocationHandler implements InvocationHandler {

private final Map<Method, HsfMethodHandler> handlers;

public HsfInvocationHandler(Map<Method, HsfMethodHandler> handlers) {
this.handlers = handlers;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
log.error(e.getMessage(), e);
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return handlers.get(method).invoke(args);
}

@Override
public boolean equals(Object obj) {
if (obj instanceof HsfInvocationHandler) {
Map<Method, HsfMethodHandler> other = ((HsfInvocationHandler) obj).handlers;
return other.equals(handlers);
}
return false;
}

@Override
public int hashCode() {
return handlers.hashCode();
}

@Override
public String toString() {
return handlers.toString();
}

}

最后就是就是HsfMethodHandler的一个具体实现,包括上面所提到的Request参数的构造,一个invoke方法的调用。

总结

  • 其实通过HttpClient的方式去调用也不是不行,只是说如果通过参考别人的代码,做一个RPC调用底层原理的一个分析,我们是可以做到一些系统层面的封装的,而且这个jar包是可以做成plugin的方式去提供给别人用的。
  • 了解动态代理的原理,可以做到对代码项目无感知或者少感知的作用。
  • 通过该过程举一反三,其他的场景都可以复制该思路去做事情。

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

简栈文化服务订阅号