Loading of extension points during Dubbo Publishing

Posted by mattbrad on Mon, 29 Nov 2021 12:41:50 +0100

During the publishing of Dubbo service, the extension point is loaded for the first time by loading the ConfiguratorFactory configuration factory in the doExportUrlsFor1Protocol() method.

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

First, obtain the extension class loader according to the loading class type, and then obtain the specific extension point according to the extension point name. First get the extender of the corresponding type from the cache. If it does not exist in the cache, use the constructor to create a new loader and put it into the cache.

public class ExtensionLoader<T> {

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }


    public boolean hasExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        try {
            this.getExtensionClass(name);
            return true;
        } catch (Throwable t) {
            return false;
        }
    }

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            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;
    }

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

The calling procedure is:

getExtensionClass(String name)
  ->getExtensionClass(String name)

The process of creating DubboProtocol is, which contains multiple wrappers:

Dubbo is URL driven.

. getAdaptiveExtension(): the obtained Adaptive class is a dynamic proxy class that dynamically generates class code according to the @ Adaptive extension point and is compiled.
Another common method is: getExtension: used to get wide extension class instances.

Protocol is Protocol$Adaptive, and the actual call is DubboProtocol
Proxyfactor is proxyfactor $adaptive. The actual call is JavassistProxyFactory, which is used to generate proxy classes and callers.
The code here means to expose the caller of a proxy class generated using JavassistProxyFactory locally using the DubboProtocol protocol.

    private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }

The injectExtension() method is used to process the method with the method name set() and dynamically set the processing parameters. For example, the protocol here is dynamic and depends on the parameters configured in the URL, so an injection method is added. In the injectExtension() method, judge the method starting with set(), get a dynamic proxy class Type$Adaptive through Object object = objectFactory.getExtension(pt, property), and then set the dynamic proxy object to the parameter

public class StubProxyFactoryWrapper implements ProxyFactory {
    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
}
public class ExtensionLoader<T> {
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
}

Another common method is getactivateextension (URL, string [] values, string group): get the activation point

@SPI
public interface ExtensionFactory {

}
@SPI Annotation to provide access ExtensionFactory Implementation class: public class SpiExtensionFactory
 Among them isWrapperClass The judgment rule is: whether the constructor of the implementation class contains loading class parameters. If yes, it is judged as a wrapper class and the wrapper class is added to the cachedWrapperClasses In the member variable, it is not added in the loading class.
@Adaptive annotation:When a class is labeled@Adaptive When commenting, in loadClass In the process of judgment, there are@Adaptive Annotation, the loaded class will be added to the cachedAdaptiveInstance Member variable as the return object when the extension point class is loaded.

be careful JavassistProxyFactory In class getInvoker()method, final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);

##
@Activate Annotation in Filter There are use cases in:

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {
}

In call export()In the process of method, first Filter Loaded.
Phased summary export()Process:
stay export()In the procedure, the wrapper is called first DubboProtocol of Wrapper Class, including three classes, from outside to inside: QosProtocolWrapper,ProtocolListenerWrapper,ProtocolFilter,The outer layer calls the inner layer level by level, and then calls ProtocolFilterWrapper Class, a method to build the call chain is called, protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER)),among Constants.SERVICE_FILTER_KEY=service.filter;Constants.PROVIDER = provider,Among them, in URL In the parameters of, service.filter of value by null,So it's loading filter After all the implementation classes of, according to Activate Condition pair Filter Filter; The filter conditions here are: group by group;Qualified in this process Filtle There are 8:
```java
0 = {ExceptionFilter@11068} 
1 = {ClassLoaderFilter@11268} 
2 = {EchoFilter@11442} 
3 = {MonitorFilter@11832} 
4 = {GenericFilter@11974} 
5 = {TimeoutFilter@11997} 
6 = {TraceFilter@12018} 
7 = {ContextFilter@12032} 

Then loop the Filter, create an invoker, and call the invoker method of the previous invoker.
Then we call the export() method of InjvmProtocol to expose the service, and construct a InjvmExporter object.

    InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;//key=com.bail.user.service.IUserService:1.0.0
        this.exporterMap = exporterMap;
        exporterMap.put(key, this);//Put the InjvmExporter itself into the map container
    }

The Exporter returned by the exportLocal method is of type ListenerExporterWrapper, and the object is put into the exporters container. Exporters is a member variable of ServiceBean.
The value of export protocol at ServiceConfig is registry

Next, let's look at the publishing process of DubboProtocol:
Building DubboExporter
openServer

Topics: Spring