background

When we use Dubbo to make a service call, we usually jar the service provider’s API to the consumer so that the consumer can refer to it directly, but we consider the following two situations.

  • What if the user of Dubbo is not a Java client?
  • If the service has a gateway layer, the gateway forwards requests to the back end. If the service relies on the back end JAR and the gateway publishes frequently, how can the stability be guaranteed?

To solve this problem, Dubbo designed generalization capabilities, which simply means that clients can be independent of producer apis.

Used to introduce

Let’s start with a simple example and then talk about the principles and implementation.

Producer allocation

//xml
<dubbo:service interface="com.poizon.study.api.service.HelloService" ref="helloService"/>
// Service implementation class
@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public WishRequest sayHelloToDubbo(String name) throws IOException {
        WishRequest wishRequest = new WishRequest();
        wishRequest.setMoney(1L);
        wishRequest.setMsg("hello to dubbo");
        return wishRequest;
    }
    @Override
    public String sayHappyNewYear(WishRequest wish) { return "thank you!"; }}Copy the code

Service consumer

//xml
<dubbo:reference generic="true" id="helloService" interface="com.poizon.study.api.service.HelloService" />

public static void main(String[] args) throws IOException, InterruptedException {        
  ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
  ctx.start();
  GenericService helloService = ctx.getBean("helloService", GenericService.class);
  Object sayHelloToDubbo = helloService.$invoke("sayHelloToDubbo".new String[]{"java.lang.String"}, new Object[]{"jack"});
  System.out.println(sayHelloToDubbo);
  //print:{msg=hello to dubbo, money=1, class=com.poizon.study.api.service.request.WishRequest, age=null}
}
Copy the code

The generic=true argument is the only difference between the generic=true argument and the main function that converts beans to GenericService objects. It then invokes the name of the method we want to invoke with its $invokeAsync ($invokeAsync). The second parameter is the parameter list, the third parameter is the pass parameter, and the custom object can be in the form of map.

The principle of analysis

The call is successful. Dubbo achieves generalization mainly through two filters (analyzed in the previous article). Let’s take these two filters out for analysis.

  • GenericFilter (Producer side)
  • GenericImplFilter (configured on the consumer sidegeneric=trueWill load)

GenericImplFilter

//org.apache.dubbo.rpc.filter.GenericImplFilter#invoke
public Result invoke(Invoker
        invoker, Invocation invocation) throws RpcException {
        String generic = invoker.getUrl().getParameter(GENERIC_KEY);
		 if((invocation.getMethodName().equals($INVOKE) || invocation.getMethodName().equals($INVOKE_ASYNC)) && invocation.getArguments() ! =null
                && invocation.getArguments().length == 3
                && ProtocolUtils.isGeneric(generic)) {
            Object[] args = (Object[]) invocation.getArguments()[2];
            if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                for (Object arg : args) {
                    if(! (byte[].class == arg.getClass())) {
                        error(generic, byte[].class.getName(), arg.getClass().getName()); }}}else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                for (Object arg : args) {
                    if(! (arginstanceof JavaBeanDescriptor)) {
                        error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                    }
                }
            }
            invocation.setAttachment(
                    GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));/ / 1
        }
        return invoker.invoke(invocation);
    }
Copy the code

The filter mainly determines the customer serialization type. Note 1 passes the serialization identifier to the producer through the Attachment (dubbo additional information parameter).

GenericFilter

//org.apache.dubbo.rpc.filter.GenericFilter#invoke
public Result invoke(Invoker
        invoker, Invocation inv)  {
if((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC)) && inv.getArguments() ! =null
        && inv.getArguments().length == 3
        && !GenericService.class.isAssignableFrom(invoker.getInterface())) {/ / 1
    String name = ((String) inv.getArguments()[0]).trim();
    String[] types = (String[]) inv.getArguments()[1];
    Object[] args = (Object[]) inv.getArguments()[2];
    try{ Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types); Class<? >[] params = method.getParameterTypes();if (args == null) {
            args = new Object[params.length];
        }
        String generic = inv.getAttachment(GENERIC_KEY);

        if (StringUtils.isBlank(generic)) {
            generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
        }

        if (StringUtils.isEmpty(generic)
                || ProtocolUtils.isDefaultGenericSerialization(generic)) {/ / 2
            args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
        } else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
            / /...
        } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
            / /...
        } else if (ProtocolUtils.isProtobufGenericSerialization(generic)) {
            / /...
        }
        return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));/ / 3
    } catch (ClassNotFoundException e) {
        throw newRpcException(e.getMessage(), e); }}return invoker.invoke(inv);
}
Copy the code

In the consumer filter, comment 1 determines whether the incoming method name is $invoke class name, parameter number, etc., and then creates the required method object by using these parameters. Note 3 creates a new RpcInvocation object as the invocation chain argument (the method name has been modified to sayHelloToDubbo, the first argument from our $invoke invocation).

Thinking about?

We change the name of the method called by the consumer to a non-existent name sayHelloToDubbo2

    public static void main(String[] args) throws IOException, InterruptedException {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
        ctx.start();
        GenericService helloService = ctx.getBean("helloService", GenericService.class);
        Object sayHelloToDubbo = helloService.$invoke("sayHelloToDubbo2".new String[]{"java.lang.String"}, new Object[]{"jack"});
        System.out.println(sayHelloToDubbo);
Copy the code
Exception in thread "main" org.apache.dubbo.rpc.RpcException: 
Failed to invoke the method sayHelloToDubbo2 in the 
service org.apache.dubbo.rpc.service.GenericService. 
Tried 3 times of the providers [192.1682.172.:20880] (1/1) from the registry localhost:2181 on the consumer 192.1682.172. using the dubbo version 2.73.. Last error is: Failed to invoke remote method: $invoke, provider: dubbo:/ / 192.168.2.172:20880 / com. Poizon. Study. API. Service. HelloService? anyhost=true&application=dubbo-provider&bean.name=com.poizon.study.api.service.HelloService&check=false&deprecated=false & dubbo = 2.0.2 & dynamic = true&generic = true&interface = com. Poizon. Study. API. Service. HelloService&lazy = false&logger = slf4j & metho Ds = sayHappyNewYear, sayHelloToDubbo&pid = 81910 & register = true&register. IP = 192.168.2.172 & release = 2.7.3 & remote. Application = du bbo-provider&side=consumer&sticky=false&timestamp=1612017946014, cause: org.apache.dubbo.remoting.RemotingException: org.apache.dubbo.rpc.RpcException: com.poizon.study.api.service.HelloService.sayHelloToDubbo2(java.lang.String)
org.apache.dubbo.rpc.RpcException: com.poizon.study.api.service.HelloService.sayHelloToDubbo2(java.lang.String)
	at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:143)
	at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
	at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
//....
Copy the code

2. 原 文 : The error was Tried 3 times, so why did the customer call it 3 times? If you are interested in using string instead of integer to try 3 times, can you avoid this error?

conclusion

Generalization is still very much in use in springboot + Dubbo ecosystem.