Deep parsing of xxl-rpc service callers

Posted by PascalNouma on Sun, 21 Jul 2019 13:52:56 +0200

I. Callers of services

Overview of service invokers:

The invoker package in the remoting package is the service caller, including configuration, bean proxy, load balancing strategy, invocation scheme, etc.

II. Generating Agent

2.1 @XxlRpcReference

Let's first look at the @XxlRpcReference annotation, which defines some of the strategies used by service callers.

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface XxlRpcReference {
    //Communication mode, default netty
    NetEnum netType() default NetEnum.NETTY;
    //Serialization, default hessian
    Serializer.SerializeEnum serializer() default Serializer.SerializeEnum.HESSIAN;
    // Call mode, default sync
    CallType callType() default CallType.SYNC;
    // Load balancing strategy, default round
    LoadBalance loadBalance() default LoadBalance.ROUND;

    // version
    String version() default "";
    // timeout
    long timeout() default 1000;
    // Service Provider Address, which you can configure on your own
    String address() default "";
    // token is used for validation
    String accessToken() default "";

    //XxlRpcInvokeCallback invokeCallback() ;

}
2.2 XxlRpcSpringInvokerFactory class

If you use spring, you can use rpc in the form of annotations + XxlRpc Spring InvokerFactory.
First of all, look at the inheritance relationship of this kind:

public class XxlRpcSpringInvokerFactory extends InstantiationAwareBeanPostProcessorAdapter implements InitializingBean,DisposableBean, BeanFactoryAware 

Inherit the InstantiationAwareBean PostProcessor Adapter and implement the InitializingBean, Disposable Bean and BeanFactoryAware interfaces.
Talk about the timing of their calls (in order):

BeanFactoryAware: Inject beanfactory for use. You can use beanfactory to get beans in spring. He has a setBeanFactory method on his side.
InitializingBean: Call the afterPropertiesSet method after the bean instantiates the properties
 InstantiationAwareBeanPostProcessor Adapter: Enhance bean s after instantiation. Call the postProcessAfterInstantiation method.
Disposable Bean: The destroy method is called when the bean is destroyed.

The order of execution is clear.
setBeanFactory() ----> afterPropertiesSet() ---->postProcessAfterInstantiation() ---->destroy().
It can be said that the first three methods are related to service caller initialization. Let's look next to each other.

2.2.1 setBeanFactory

This is very simple. It refers to the beanfactory as a member variable.

private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
2.2.2 afterPropertiesSet
 @Override
    public void afterPropertiesSet() throws Exception {
        // start invoker factory
        xxlRpcInvokerFactory = new XxlRpcInvokerFactory(serviceRegistryClass, serviceRegistryParam);
        xxlRpcInvokerFactory.start();
    }

Create the XxlRpcInvokerFactory object and call the start method. Next, let's look at this class.
2.2.2.1 XxlRpcProviderFactory
XxlRpcProviderFactory class mainly does service registration, client thread pool response initialization and callback thread pool initialization, client start, stop work.
Let's look at the start method:
The corresponding registry object is created according to the configuration, and the start method of the registry is invoked. There is no detailed analysis here. The registry start method is mainly about creating registry client and creating daemon threads to refresh the list of access service providers.

 public void start() throws Exception {
        // start registry
        if (serviceRegistryClass != null) {
        	// Creating Registry Objects
            serviceRegistry = serviceRegistryClass.newInstance();
            // Call the start method of the registry object  
            serviceRegistry.start(serviceRegistryParam);
        }
    }

Next, look at the stop method:

 public void  stop() throws Exception {
        // Stop registry stop registry client
        if (serviceRegistry != null) {
            serviceRegistry.stop();
        }

        // Stop callback executes stop callback method
        if (stopCallbackList.size() > 0) {
            for (BaseCallback callback: stopCallbackList) {
                try {
                    callback.run();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        // Stop Callback ThreadPool Closes Thread Pool
        stopCallbackThreadPool();
    }
2.2.3 postProcessAfterInstantiation

Look at this code first, and add comments at the key points. It's very simple and easy to understand.

 @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {

        // collection
        final Set<String> serviceKeyList = new HashSet<>();

        // parse XxlRpcReferenceBean
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                // Does the field have XxlRpcReference annotations
                if (field.isAnnotationPresent(XxlRpcReference.class)) {
                    // valid gets the type of field
                    Class iface = field.getType();
                    // Field is not an exception thrown by the interface
                    if (!iface.isInterface()) {
                        throw new XxlRpcException("xxl-rpc, reference(XxlRpcReference) must be interface.");
                    }
                    // Get annotation information.
                    XxlRpcReference rpcReference = field.getAnnotation(XxlRpcReference.class);

                    // Init reference beans create reference beans, set some required parameters, and set the parameters in the annotations to the reference bean object.
                    XxlRpcReferenceBean referenceBean = new XxlRpcReferenceBean(
                            rpcReference.netType(),
                            rpcReference.serializer().getSerializer(),
                            rpcReference.callType(),
                            rpcReference.loadBalance(),
                            iface,
                            rpcReference.version(),
                            rpcReference.timeout(),
                            rpcReference.address(),
                            rpcReference.accessToken(),
                            null,
                            xxlRpcInvokerFactory
                    );
                    // Call the getObject method, which mainly uses the generation of proxy objects.
                    Object serviceProxy = referenceBean.getObject();

                    // set bean
                    field.setAccessible(true);
                    //Assign a value to this field 
                    field.set(bean, serviceProxy);

                    logger.info(">>>>>>>>>>> xxl-rpc, invoker factory init reference bean success. serviceKey = {}, bean.field = {}.{}",
                            XxlRpcProviderFactory.makeServiceKey(iface.getName(), rpcReference.version()), beanName, field.getName());

                    // collection generates service key based on the full class name and version of the interface, and looks at it with the service provider
                    String serviceKey = XxlRpcProviderFactory.makeServiceKey(iface.getName(), rpcReference.version());
                    serviceKeyList.add(serviceKey);

                }
            }
        });
  
        // mult discovery performs service discovery, caching the service provider of this key locally
        if (xxlRpcInvokerFactory.getServiceRegistry() != null) {
            try {
                xxlRpcInvokerFactory.getServiceRegistry().discovery(serviceKeyList);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }

        return super.postProcessAfterInstantiation(bean, beanName);
    }

This method mainly finds out the member variables with XxlRpcReference annotation. Then parse the parameters in the XxlRpcReference annotation, create the XxlRpcReference Bean object, generate the proxy object, and assign the proxy object to the field, so that when we use the service, the proxy object is really called. Next we'll look at the XxlRpcReferenceBean class.
2.2.3.1 XxlRpcReferenceBean
Proxy objects can be generated from this class, which is what we often call client stub.
First, let's look at the fields and constructions of this class.

private NetEnum netType;
	private Serializer serializer;  // Serialization approach, hession
	private CallType callType;  // Call mode
	private LoadBalance loadBalance;  //Load Balancing Strategy

	private Class<?> iface;  // Interface class object
	private String version;  // Edition

	private long timeout = 1000; //timeout

	private String address;  // Service Provider Address
	private String accessToken;// token

	private XxlRpcInvokeCallback invokeCallback;

	private XxlRpcInvokerFactory invokerFactory;  // invoker factory

	public XxlRpcReferenceBean(NetEnum netType,
							   Serializer serializer,
							   CallType callType,
							   LoadBalance loadBalance,
							   Class<?> iface,
							   String version,
							   long timeout,
							   String address,
							   String accessToken,
							   XxlRpcInvokeCallback invokeCallback,
							   XxlRpcInvokerFactory invokerFactory
	) {

		this.netType = netType;
		this.serializer = serializer;
		this.callType = callType;
		this.loadBalance = loadBalance;
		this.iface = iface;
		this.version = version;
		this.timeout = timeout;
		this.address = address;
		this.accessToken = accessToken;
		this.invokeCallback = invokeCallback;
		this.invokerFactory = invokerFactory;

		// valid
		if (this.netType==null) {
			throw new XxlRpcException("xxl-rpc reference netType missing.");
		}
		if (this.serializer==null) {
			throw new XxlRpcException("xxl-rpc reference serializer missing.");
		}
		if (this.callType==null) {
			throw new XxlRpcException("xxl-rpc reference callType missing.");
		}
		if (this.loadBalance==null) {
			throw new XxlRpcException("xxl-rpc reference loadBalance missing.");
		}
		if (this.iface==null) {
			throw new XxlRpcException("xxl-rpc reference iface missing.");
		}
		if (this.timeout < 0) {
			this.timeout = 0;
		}
		if (this.invokerFactory == null) {
			this.invokerFactory = XxlRpcInvokerFactory.getInstance();
		}

		// init Client
		initClient();
	}

At the end of the construction, an initClient method is called. This method is mainly to create client objects and initialize parameters.
Then let's look at the service caller core: generation of proxy objects
The getObject method is to generate a proxy object for a service. Using technology is the proxy of jdk.

	public Object getObject() {
		return Proxy.newProxyInstance(Thread.currentThread()
				.getContextClassLoader(), new Class[] { iface },
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

						// method param generates some call parameters
						String className = method.getDeclaringClass().getName();	// iface.getName()
						String varsion_ = version;
						// Method name
						String methodName = method.getName();
						//Method parameter type
						Class<?>[] parameterTypes = method.getParameterTypes();
						// Method parameter values
						Object[] parameters = args;

						// filter for generic
						// This is to determine whether it is a generalized call. If it is a generalized call, the parameters are configured according to the user, such as which class you want to call, method name, parameter type, parameter value. All these are necessary.
						// Set up
						if (className.equals(XxlRpcGenericService.class.getName()) && methodName.equals("invoke")) {

							Class<?>[] paramTypes = null;
							if (args[3]!=null) {
								String[] paramTypes_str = (String[]) args[3];
								if (paramTypes_str.length > 0) {
									paramTypes = new Class[paramTypes_str.length];
									for (int i = 0; i < paramTypes_str.length; i++) {
										paramTypes[i] = ClassUtil.resolveClass(paramTypes_str[i]);
									}
								}
							}

							className = (String) args[0];
							varsion_ = (String) args[1];
							methodName = (String) args[2];
							parameterTypes = paramTypes;
							parameters = (Object[]) args[4];
						}	
						// This is to confirm what kind of method you came from.
						// filter method like "Object.toString()"
						if (className.equals(Object.class.getName())) {
							logger.info(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}#{}]", className, methodName);
							throw new XxlRpcException("xxl-rpc proxy class-method not support");
						}
						
						// The choice of service provider address is based on load balancing strategy
						String finalAddress = address;
						if (finalAddress==null || finalAddress.trim().length()==0) {
							if (invokerFactory!=null && invokerFactory.getServiceRegistry()!=null) {
								// discovery
								String serviceKey = XxlRpcProviderFactory.makeServiceKey(className, varsion_);
								TreeSet<String> addressSet = invokerFactory.getServiceRegistry().discovery(serviceKey);
								// load balance
								if (addressSet==null || addressSet.size()==0) {
									// pass
								} else if (addressSet.size()==1) {  // When it's just one, choose the first one.
									finalAddress = addressSet.first();
								} else { // load balancing
									finalAddress = loadBalance.xxlRpcInvokerRouter.route(serviceKey, addressSet);
								}

							}
						}
						
						// The last call address or null throws an exception
						if (finalAddress==null || finalAddress.trim().length()==0) {
							throw new XxlRpcException("xxl-rpc reference bean["+ className +"] address empty");
						}

						// Request request parameter blocking
						XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
	                    xxlRpcRequest.setRequestId(UUID.randomUUID().toString());
	                    xxlRpcRequest.setCreateMillisTime(System.currentTimeMillis());
	                    xxlRpcRequest.setAccessToken(accessToken);
	                    xxlRpcRequest.setClassName(className);
	                    xxlRpcRequest.setMethodName(methodName);
	                    xxlRpcRequest.setParameterTypes(parameterTypes);
	                    xxlRpcRequest.setParameters(parameters);
	                    
	                    // send is invoked according to the invocation policy
						if (CallType.SYNC == callType) {
							// future-response set
							XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
							try {
								// do invoke
								client.asyncSend(finalAddress, xxlRpcRequest);

								// future get
								XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
								if (xxlRpcResponse.getErrorMsg() != null) {
									throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
								}
								return xxlRpcResponse.getResult();
							} catch (Exception e) {
								logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);

								throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
							} finally{
								// future-response remove
								futureResponse.removeInvokerFuture();
							}
						} else if (CallType.FUTURE == callType) {
							// future-response set
							XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
                            try {
								// invoke future set
								XxlRpcInvokeFuture invokeFuture = new XxlRpcInvokeFuture(futureResponse);
								XxlRpcInvokeFuture.setFuture(invokeFuture);

                                // do invoke
                                client.asyncSend(finalAddress, xxlRpcRequest);

                                return null;
                            } catch (Exception e) {
								logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);

								// future-response remove
								futureResponse.removeInvokerFuture();

								throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
                            }

						} else if (CallType.CALLBACK == callType) {

							// get callback
							XxlRpcInvokeCallback finalInvokeCallback = invokeCallback;
							XxlRpcInvokeCallback threadInvokeCallback = XxlRpcInvokeCallback.getCallback();
							if (threadInvokeCallback != null) {
								finalInvokeCallback = threadInvokeCallback;
							}
							if (finalInvokeCallback == null) {
								throw new XxlRpcException("xxl-rpc XxlRpcInvokeCallback(CallType="+ CallType.CALLBACK.name() +") cannot be null.");
							}

							// future-response set
							XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, finalInvokeCallback);
							try {
								client.asyncSend(finalAddress, xxlRpcRequest);
							} catch (Exception e) {
								logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);

								// future-response remove
								futureResponse.removeInvokerFuture();

								throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
							}

							return null;
						} else if (CallType.ONEWAY == callType) {
                            client.asyncSend(finalAddress, xxlRpcRequest);
                            return null;
                        } else {
							throw new XxlRpcException("xxl-rpc callType["+ callType +"] invalid");
						}

					}
				});
	}

Although this method seems very long, it is not very troublesome. It mainly does these things:
1. Get some parameters of the request, including which class you want to adjust, which method, method parameter type, method parameter value.
2. If it is a generic call (you specify the fields in 1) class, method name, method parameter type, method parameter should be according to your specified.
3. Choosing the address of the service provider, according to the load balancing strategy, you have the lowest priority in your annotations (which is a bit confusing).
4. Encapsulate request parameters, encapsulate classes, method names, method parameter types, method parameter values, and a unique uuid into Request object entities.
5. Call according to the calling strategy.

summary

This article mainly talks about the general process of xxl-rpc service caller generation, from configuration to registry connection to generate reference beans to generate proxy objects. Through this article, we can understand that the RPC service caller actually generates a call object in this service. There is no in-depth explanation of the details, such as call strategy, client network communication, load balancing and so on. These follow-up chapters will be explained in detail.

Topics: Programming Spring Netty JDK Load Balance