SPI Adaptive Extended Adaptive Principle for Dubbo Source Analysis 2

Keywords: Dubbo Java Spring JDK

Catalog

  • Principle overview
  • Why design the @Adaptive annotation
  • Where, when and how dubbo source is used
  • Source Code Analysis for @Adaptive Extension Loading Process
  • summary

Principle overview

First, look at the description of adaptive scaling on the following website:

In Dubbo, many extensions are loaded through SPI mechanisms, such as Protocol, Cluster, LoadBalance, and so on.Sometimes, some extensions do not want to be loaded during the framework startup phase, but rather load based on runtime parameters when the extensions method is called.That sounds conflicting.If the extension is not loaded, then the extension method cannot be invoked (except for the static method).The extension method is not called, and the extension cannot be loaded.For this contradictory problem, Dubbo solves it well through the adaptive expansion mechanism.The implementation logic of the adaptive expansion mechanism is complex. First, Dubbo generates proxy-capable code for the expansion interface.This code is then compiled through javassist or jdk to get the Class class.Finally, the proxy class is created through reflection, which is a complicated process.

As abstract as it seems, first look back at what dubbo's SPI is: SPI is an extension mechanism for getting an object.Obtained through ExtensionLoader.getExtension(String name) and ExtensionLoader.getAdaptiveExtension(), how does the adaptivity of this extension class fit in the right way? Keep looking down

Looking at the source definition of the @Adaptive annotation, you can see that it can be used on methods and classes

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

Why design the @Adaptive annotation

Adaptive annotations are designed to distinguish between manually coded and dynamically generated classes

When annotating on a class, the logic representing the extension is completed by manual coding, and there is no need to generate a dynamic proxy class (the corresponding java file already exists in the project source code); in Dubbo, only two classes are annotated by Adaptive, namely, AdaptiveCompiler and AdministratoveExtensionFactory; AdaptiveCompiler has only two implementation classes, JavassistCompiler and JdkCompiler;There are only two implementation classes for nsionFactory, SpiExtensionFactory and Spring ExtensionFactory.

Annotations on interface methods indicate that the logic representing the extension is automatically generated by a framework (corresponding java files are dynamically stitched strings as needed after startup, compiled and loaded); for example, the Protocol interface, the implementation classes are DubboProtocol, InjvmProtocol, HttpProtcol, etc.

The load process for the extension is then analyzed using AdaptiveCompiler and Proocol as examples

Where, when and how dubbo source is used

The following starts with the most basic xml configuration file:

<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

Learn from spring.handlers about resolving DubboNamespaceHandler

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }
}

From registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); learn the parsing service label
ServiceBean object when signing corresponding, ServiceBean inherits ServiceConfig

public class ServiceConfig<T> extends AbstractServiceConfig {

    private static final long serialVersionUID = 3033787999037024738L;

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

    private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
    private final List<URL> urls = new ArrayList<URL>();
    private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

There are two static variable initializations in ServiceConfig
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
Now that you've used it, you'll analyze Protocol s'adaptive extension loading process

Source Code Analysis for @Adaptive Extension Loading Process

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Break the breakpoint on this line, first into the getAdaptiveExtension() method
Code Block 1.1

public T getAdaptiveExtension() {
//Find from cache first
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
//Not in the first entry cache, will be here
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

Find from the cache first, create without it, and enter createAdaptiveExtension()
Code Block 2.1

private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

dubbo's IOC implementation at injectExtension(), as mentioned in the previous article;
Next, look at the getAdaptiveExtensionClass() method:
Code Block 3.1

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

getExtensionClasses(); This method is important, mainly by reading the configuration in the MET-INF/dubbo/internal directory to get the alias and class name of the extension, and by judging whether there is an @Adaptive annotation on the class and adding the cachedAdaptiveClass cached variable, some code is as follows: This cache contains the classes with @Adaptive annotation on the class mentioned earlier
Code Block 4.1

                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                                if (cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                } else if (!cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {

Returns the getAdaptiveExtensionClass() method, which is empty, so the createAdaptiveExtensionClass() method is executed as follows:
Code Block 4.2

private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

Line by line analysis:
String code = createAdaptiveExtensionClassCode();
This line of code produces a Protocol$Adaptice object by splicing a string. This is where the preceding comment on the method is used. The adaptive extension is reflected in this object, and the code is as follows:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

True adaptive is reflected here, loading an implementation on demand, extName, using the default dubbo value if no value is passed in the url, or passed in.

Back in line 4.2,

com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

We found that the getAdaptiveExtension() method was called here again, circularly?This is where the @Adaptive annotation loads classes, so you don't need to generate dynamic classes at this time

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

The cachedAdaptiveClass is not empty when this method is called again, so it returns directly to exit the loop.

Note: Each time a different extension is loaded, a new ExtensionLoader instance is used

summary

This paper describes when the adaptive extension mechanism is used, the purpose of the @Adaptive design, the differences in methods and classes annotated, and how the source code is implemented.
The Adaptive @Adaptive principle is also finished here. If you want to see the source dubbo SPI and the adaptive principle, you have to understand that Extension Loader is used everywhere in the source code.

52 original articles published. 8% praised. 130,000 visits+
Private letter follow

Posted by robot43298 on Sat, 11 Jan 2020 16:43:24 -0800