How can I get through SpringCloud and HSF calls?

background

In 2019 we experienced a whole year of migrations, including a switchover of the RPC framework.The HSF RPC framework that we used before comes from Alibaba. After years of high concurrency of Double 11, there is no doubt that there are no problems with high performance. It also supports both TCP and HTTP. The only bad thing is that it is not open source. If there are problems, there are really some problems and risks.

So, in order to embrace open source, SpringCloud is used. Calls between systems are made through FeignClient, but some of the underlying systems cannot respond as positively in a short time due to time, labor, history and so on.So there is a situation where SpringCloud and HSF services exist at the same time, so a proxy tool is written in order that everyone can make local calls (TCP, FeignClient) during re-encoding.

Interaction Diagram

If this is the case, we can still feel that each time we make an Http request through HttpClient, etc., the experience of writing code is not very good.

To solve this problem, our task is to write a proxy encapsulation.

Analysis Function Points

Learn about FeignClient

Let's refer to a process of parsing FeignClient's functionality, as shown in the following figure:

  • Generate dynamic proxy classes
  • Resolve an equal MethodHandler
  • Dynamic Generation Request
  • Encoder
  • Interceptor handling
  • Log Processing
  • Retry mechanism
What do agents need to consider?

That's not as good as writing. Our first goal is to scan proxy send requests.

Since the parameters of the HSF do not correspond to the standard Http method, the format of the message needs to be specially structured when making an Http request

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

Code Framework Implementation

SpringBoot Total Entry, open the @EnableHsfClients annotation

@SpringBootApplication
@EnableHsfClients(basePackages = "com.cyblogs.client.hsf")
public class App {

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

Here you define the packages to scan, the specific classes, and so on

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

    String[] basePackages() default {};

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

}

Use Spirng's Import Bean DefinitionRegistrar for automatic injection to generate beans.

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("/")) {//Avoid setting url to'schema:ip:port/'format
            url = url.substring(0, url.length() - 1);
        }
        try {
            new URL(url);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException(url + " is malformed", e);
        }
        return url;
    }
}

HsfClientFactoryBean Definition

@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 ignored temporarily
            } 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();
    }
}

Implementation of proxy class

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

}

Finally, there is a specific implementation of HsfMethodHandler, including the construction of the Request parameter mentioned above, and a call to an invoke method.

summary

  • In fact, it is not bad to call it by HttpClient, just to say that if we make an analysis of the underlying principle of RPC call by referencing someone else's code, we can do some system-level encapsulation, and this jar package can be made into a plugin to provide others with use.
  • Understanding the principle of dynamic proxy allows you to be less or less aware of code items.
  • Through this process, all other scenarios can copy this idea to do something.

If you like my article, you can focus on your personal subscription number.Welcome to leave a message and exchange at any time.If you want to join the WeChat group and discuss it together, add lastpass4u, the administrator's culture assistant, who will pull you into the group.

Tags: Programming less encoding curl SpringBoot

Posted on Wed, 06 May 2020 12:19:30 -0400 by rhosk