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 DiagramIf 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 FeignClientLet'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
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/queryNewConfigCode 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.