如何打通SpringCloud与HSF的调用?

背景

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

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

交互图

如何打通SpringCloud与HSF的调用?

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

如何打通SpringCloud与HSF的调用?

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

分析功能点

了解一下FeignClient

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

如何打通SpringCloud与HSF的调用?

  • 生成动态代理类
  • 解析出等的MethodHandler
  • 动态生成Request
  • Encoder
  • 拦截器处理
  • 日志处理
  • 重试机制

代理需要考虑什么?

如何打通SpringCloud与HSF的调用?

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

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

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);
    }
}

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ HsfClientsRegistrar.class })
public @interface EnableHsfClients {  
    String[] value() default {};

    String[] basePackages() default {};

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

}

利用 SpirngImportBeanDefinitionRegistrar 来进行自动注入生成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();
    }
}

代理类的实现

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),他会拉你们进群。

如何打通SpringCloud与HSF的调用?

原文 

http://www.cyblogs.com/ru-he-da-tong-springcloudyu-hsfde-diao-yong/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 如何打通SpringCloud与HSF的调用?

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址