Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my wechat “JAVA_front” to communicate and learn together


1 Generalization call instance

For JAVA server developers, we don’t use generic calls very often when using Dubbo. The usual approach is that after the producer publishes the service, the consumer can invoke it by importing a producer-provided client. So what are the generalization call usage scenarios?

In the first scenario, the consumer does not want to introduce producer-provided client dependencies and only wants to focus on which methods are called and which parameters need to be passed. The second scenario is that the consumer does not use the Java language, but uses, for example, Python. How do you invoke a service provided by a Java language producer? At this point we can choose to generalize the call.

The method used by a generalization call is not complicated, so let’s write an example of a generalization call. First, the producer publishes the service, which is no different from normal service publishing.

package com.java.front.dubbo.demo.provider;

public interface HelloService {
    public String sayHelloGeneric(Person person, String message);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHelloGeneric(Person person, String message) throws Exception {
        String result = "hello[" + person + "],message=" + message;
        returnresult; }}Copy the code

The Person class declares:

package com.java.front.dubbo.demo.provider.model;

public class Person implements Serializable {
    private String name;
}
Copy the code

The content of the provider. XML file:

<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	<! -- Provider application information for calculating dependencies -->
	<dubbo:application name="java-front-provider" />

	<! -- Connect to the registry -->
	<dubbo:registry address=Zookeeper: / / "127.0.0.1:2181" />

	<! -- Producer 9999 exposes service on port -->
	<dubbo:protocol name="dubbo" port="9999" />
	
	<! -- Bean -->
	<bean id="helloService" class="com.java.front.dubbo.demo.provider.HelloServiceImpl" />
	
	<! -- Exposure service -->
	<dubbo:service interface="com.java.front.dubbo.demo.provider.HelloService" ref="helloService" />
</beans>
Copy the code

The consumer code is different:

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.service.GenericService;

public class Consumer {
    public static void testGeneric(a) {
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
        reference.setApplication(new ApplicationConfig("java-front-consumer"));
        reference.setRegistry(new RegistryConfig(Zookeeper: / / "127.0.0.1:2181"));
        reference.setInterface("com.java.front.dubbo.demo.provider.HelloService");
        reference.setGeneric(true);
        GenericService genericService = reference.get();
        Map<String, Object> person = new HashMap<String, Object>();
        person.put("name"."Wechat official account" JAVA Front");
        String message = "Hello";
        Object result = genericService.$invoke("sayHelloGeneric".new String[] { "com.java.front.dubbo.demo.provider.model.Person"."java.lang.String" }, newObject[] { person, message }); System.out.println(result); }}Copy the code


2 Invoker

To explain the principle of generalized calls through source code analysis, we first need to understand the Dubbo heavyweight concept of Invoker. The producer exposure service process is generally divided into two steps. The first step is to convert the interface implementation class to Invoker, and the second step is to convert Invoker to Exporter and put it into ExporterMap. The exposure service flow chart is as follows:


AbstractProxyInvoker (AbstractProxyInvoker)

public class JdkProxyFactory extends AbstractProxyFactory {

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, Class
       [] parameterTypes, Object[] arguments) throws Throwable {

                / / proxy is a proxy object - > com. Java. Front. The dubbo. Demo. The provider. HelloServiceImpl
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                returnmethod.invoke(proxy, arguments); }}; }}Copy the code


Consumer reference service flow chart:


Consumer invokers are created by display instantiation, local and remote exposures are both created by display Invoker (AbstractInvoker)

new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap)
new DubboInvoker<T>(serviceType, url, getClients(url), invokers)
Copy the code

Proxyfactory. getProxy create proxy:

public class JdkProxyFactory extends AbstractProxyFactory {
    @Override
    public <T> T getProxy(Invoker
       
         invoker, Class
        [] interfaces)
        {
        InvokerInvocationHandler invokerInvocationHandler = new InvokerInvocationHandler(invoker);
        return(T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, invokerInvocationHandler); }}Copy the code

Both producers and consumers Invoker since org. Apache. Dubbo. RPC. The Invoker:

public abstract class AbstractInvoker<T> implements Invoker<T>{}public abstract class AbstractProxyInvoker<T> implements Invoker<T> {}Copy the code


3 Decorator mode

Why should both producers and consumers convert to Invoker instead of not calling it directly? I think Invoker is the beauty of Dubbo’s design: real calls are converted to Invoker, and Dubbo can enhance Invoker functionality through decorator patterns. Let’s look at what the decorator pattern is.

The decorator pattern can dynamically attach responsibility to objects, enhance primitive class functionality without changing the primitive class interface, and support nested use of multiple decorators. Implementing the decorator pattern requires the following components:

(1) Component

Core business abstraction: You can use interfaces or abstract classes

(2) ConcreteComponent

Implement core business: The business code that is ultimately executed

(3) Decorator

Abstract decorator class: Implements Component and combines a Component object

(4) ConcreteDecorator

Specific decoration content: decoration of core business code

Let’s examine a decorator instance. A football player is going to play. Let’s decorate him with sneakers and socks to increase his strength.

(1) Component

/** ** Abstract component (can be replaced by interface) */
public abstract class Component {

    /** * play football (core business method) */
    public abstract void playFootBall(a);
}
Copy the code

(2) ConcreteComponent

/** ** */
public class ConcreteComponent extends Component {

    @Override
    public void playFootBall(a) {
        System.out.println("The player kicks the ball."); }}Copy the code

(3) Decorator

/** * abstract decorator */
public abstract class Decorator extends Component {
    private Component component = null;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void playFootBall(a) {
        this.component.playFootBall(); }}Copy the code

(4) ConcreteDecorator

/** ** socks decorator */
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    /** * defines the sock decoration logic */
    private void decorateMethod(a) {
        System.out.println("Power value increased with socks.");
    }

    /** * overrides the parent method */
    @Override
    public void playFootBall(a) {
        this.decorateMethod();
        super.playFootBall(); }}/** * shoe decorator */
public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    /** * Define shoe decoration logic */
    private void decorateMethod(a) {
        System.out.println("Power increases when you change into sneakers.");
    }

    /** * overrides the parent method */
    @Override
    public void playFootBall(a) {
        this.decorateMethod();
        super.playFootBall(); }}Copy the code

(5) Test code

public class TestDecoratorDemo {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component = new ConcreteDecoratorA(component);
        component = newConcreteDecoratorB(component); component.playFootBall(); }}// Change boots to increase stamina
// The power value of socks is increased
// The player kicks the ball
Copy the code


4 Filter Link

What features does Dubbo add to Invoker? Filter chain is one of the most important features that I think is enhanced, and we continue to analyze the source code:

public class ProtocolFilterWrapper implements Protocol {

	@Override
	public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
		if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
			return protocol.export(invoker);
		}
		// Add filter chain
		Invoker<T> invokerChain = buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);
		return protocol.export(invokerChain);
	}

	@Override
	public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
		if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
			return protocol.refer(type, url);
		}
		// Add filter chain
		Invoker<T> invoker = protocol.refer(type, url);
		Invoker<T> result = buildInvokerChain(invoker, Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
		returnresult; }}Copy the code

Both producers and consumers create filter chains, so let’s look at buildInvokerChain:

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;

        // (1) load all filters that contain the Activate annotation
        // (2) Obtain the filter list according to the group filter
        // (3) The Invoker is placed at the end of the filter chain
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if(! filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;

                // Construct a simplified Invoker
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface(a) {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl(a) {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable(a) {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        // Construct a filter link
                        Result result = filter.invoke(next, invocation);
                        if (result instanceof AsyncRpcResult) {
                            AsyncRpcResult asyncResult = (AsyncRpcResult) result;
                            asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
                            return asyncResult;
                        } else {
                            returnfilter.onResponse(result, invoker, invocation); }}@Override
                    public void destroy(a) {
                        invoker.destroy();
                    }

                    @Override
                    public String toString(a) {
                        returninvoker.toString(); }}; }}returnlast; }}Copy the code

Load all the filters that contain the Activate annotation, filter the list of filters according to the group filter, and finally put the Invoker to the end of the filter chain.

EchoFilter -> ClassloaderFilter -> GenericFilter -> ContextFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter -> ExceptionFilter -> AbstractProxyInvoker
Copy the code

The consumer ultimately generates the link:

ConsumerContextFilter -> FutureFilter -> MonitorFilter -> GenericImplFilter -> DubboInvoker
Copy the code


5 Generalization call principle

We are finally about to see the core principle of the generalized call. We see the GenericFilter on the producer link and the GenericImplFilter on the consumer link, and it is these two filters that implement the generalized call.

(1) GenericImplFilter

@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {

    @Override
    public Result invoke(Invoker
        invoker, Invocation invocation) throws RpcException {

        // Method name =$invoke
        // invocation.getArguments()=("sayHelloGeneric", new String[] { "com.java.front.dubbo.demo.provider.model.Person", "Java.lang.String"}, new Object[] {person," hello "});
        if(invocation.getMethodName().equals(Constants.$INVOKE) && invocation.getArguments() ! =null
                && invocation.getArguments().length == 3
                && ProtocolUtils.isGeneric(generic)) {

            // The first argument represents the method name
            // The second parameter indicates the parameter type
            // The third parameter indicates the parameter value -> [{name= wechat public id "JAVA front"}, hello]
            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(! (arginstanceofJavaBeanDescriptor)) { error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName()); }}}// Set the generic value of the additional parameter to true
            ((RpcInvocation) invocation).setAttachment(Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
        }
        // Continue to execute the filter link
        returninvoker.invoke(invocation); }}Copy the code


(2) GenericFilter

@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {

    @Override
    public Result invoke(Invoker
        invoker, Invocation inv) throws RpcException {

        // RpcInvocation[methodName=$invoke, parameterTypes=[class java.lang.String, class [Ljava.lang.String;, class [Ljava.lang.Object;], arguments=[sayHelloGeneric, [Ljava.lang.String;@14e77f6b, [Ljava.lang.Object;@51e5f393], Attachments = {path = com. Java. Front. Dubbo. Demo. The provider. The HelloService, input = 451, dubbo = 2.0.2, Interface = com. Java. Front. Dubbo. Demo. The provider. The HelloService, version = 0.0.0, generic = true}]
        if(inv.getMethodName().equals(Constants.$INVOKE) && inv.getArguments() ! =null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {

            // sayHelloGeneric
            String name = ((String) inv.getArguments()[0]).trim();

            // [com.java.front.dubbo.demo.provider.model.Person, java.lang.String]
            String[] types = (String[]) inv.getArguments()[1];

            // [{name= wechat public id "JAVA front"}, hello]
            Object[] args = (Object[]) inv.getArguments()[2];

            // RpcInvocation[methodName=sayHelloGeneric, parameterTypes=[class com.java.front.dubbo.demo.provider.model.Person, Class java.lang.String], arguments=[Person(name= Java front), ABC], Attachments = {path = com. Java. Front. Dubbo. Demo. The provider. The HelloService, input = 451, dubbo = 2.0.2, Interface = com. Java. Front. Dubbo. Demo. The provider. The HelloService, version = 0.0.0, generic = true}]
            RpcInvocation rpcInvocation = newRpcInvocation(method, args, inv.getAttachments()); Result result = invoker.invoke(rpcInvocation); }}}Copy the code


6 Article Summary

This article begins with an introduction to how to use generalized calls and the question of why they work. The second point introduces the heavyweight concept Invoker and raises the question of why Dubbo created Invoker. The third point shows how the decorator pattern enhances functionality. Finally, we know through source code analysis that the filter chain enhances the Invoker function and is the core of the implementation of the generalized call. I hope this article will be helpful to you.

Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my wechat “JAVA_front” to communicate and learn together