背景
2019年我们经历了一整年的各种迁移,其中包括了一项RPC
框架的切换。以前我们用的HSF RPC
框架,它是来自于阿里巴巴,经过了多年的双11
高并发的洗礼,高性能这块儿毫无疑问没有任何的问题,而且它还同时支持TCP
与HTTP
的方式,唯一不太好的就是它不开源,如果出现问题定位起来确实有一些问题与风险。
所以,我们为了拥抱开源,全部采用SpringCloud
,系统与系统之间调用是通过FeignClient
的方式来调用的,但是由于底层的部分系统由于时间、人力、历史等原因,无法在短时间内都像我们一样能积极响应。所以就出现了SpringCloud
与HSF服务同时存在的情况,为了大家再编码过程中都能像本地调用(TCP
,FeignClient
),所以就写了一个代理工具。
交互图
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请求的时候,需要特殊的构造一下报文的格式
代码语言:javascript复制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
注解
@SpringBootApplication
@EnableHsfClients(basePackages = "com.cyblogs.client.hsf")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
这里定义好需要扫描的包,具体的类等
代码语言:javascript复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ HsfClientsRegistrar.class })
public @interface EnableHsfClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] clients() default {};
}
利用Spirng
的ImportBeanDefinitionRegistrar
来进行自动注入生成Bean。
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
定义
@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();
}
}
代理类的实现
代码语言:javascript复制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),他会拉你们进群。