Dubbo-SPI and Adaptive Extension Principles, 74 sets of advanced Java interviews

The class parameter here is the interface type of the extension point, and each loader needs to bind an extension point type. The loader is then fetched from the cache first, and a loader is initialized and cached before being fetched. When initializing a private constructor, we need to be aware of the objectFactory variable, which is probably an image first and will be used later. Once you get the loader, you can call the getExtension method to get the specified extension point. This method passes in a name parameter. It is not difficult to guess that this is the key in the configuration file. You can debugger to verify that:

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
	if (name == null || name.length() == 0)
	    throw new IllegalArgumentException("Extension name == null");
	if ("true".equals(name)) {
	    return getDefaultExtension();
	}
	// Gets the Holder object from the cache whose value is the extended object
	Holder<Object> holder = cachedInstances.get(name);
	if (holder == null) {
	    cachedInstances.putIfAbsent(name, new Holder<Object>());
	    holder = cachedInstances.get(name);
	}
	// The extension is not in the cache, so if the extension class has not been loaded, go to the configuration file and load and create the corresponding extension object
	// Thread security is guaranteed here by double-checking locks, which is used extensively in Dubbo
	Object instance = holder.get();
	if (instance == null) {
	    synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
	}
	return (T) instance;
}

The same is true if the cache is taken first, and the cache is created and added to the cache without it, so the main focus is on the createExtension method:

// Extended Class Instance Cache Object
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
private T createExtension(String name) {
	// Load the extension class from the configuration file and get the specified extension class, throw an exception if not
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
    	// Take an extension class instance from the cache and create and cache it without reflection
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // Dependent Injection, which has nothing to do with the current process here or later, can be skipped first, just have an impression
        injectExtension(instance);
        // Get the wrapper class, instantiate it, and finally inject the dependent object
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

The key point code is how to load extension classes from the configuration file in the getExtensionClasses method. This method mainly calls the loadExtensionClasses method:

private Map<String, Class<?>> loadExtensionClasses() {
	// Determines whether the interface is labeled with the @SPI annotation whose value is the default extension class used.
	// cachedDefaultName variable assigned to cached
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }

    // How to Really Read Configuration Files
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

This method mainly caches the default extension implementation class specified by the current extension interface (@SPI annotation) and calls loadFile to read the configuration file, from which we can see that Dubbo reads the configuration file in the following three folders by default:

 private static final String SERVICES_DIRECTORY = "META-INF/services/";
 private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

Then there's loadFile, which is a long way to go and doesn't put it all up. Here's the key code:

String fileName = dir + type.getName();

First find the corresponding file through the full path of the file, and read the contents of the file with a line of BufferedReader:

String name = null;
int i = line.indexOf('=');
if (i > 0) {
	// Keys in Configuration File
    name = line.substring(0, i).trim();
    // Extension Point Full Class Name
    line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
	// Loading classes throws exceptions if dependent jar packages for some classes are not imported (e.g. WebserviceProtocol)
    Class<?> clazz = Class.forName(line, true, classLoader);
    // Verify that the current type is the parent of an extension class
    if (! type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error when load extension class(interface: " +
                type + ", class line: " + clazz.getName() + "), class " 
                + clazz.getName() + "is not subtype of interface.");
    }
    // Whether the extension class is labeled with the @Adaptive annotation to represent a custom adaptive extension class
    // If cached to cachedAdaptiveClass
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if(cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (! cachedAdaptiveClass.equals(clazz)) {
        	// Exception thrown if more than one custom adaptive extension class
            throw new IllegalStateException("More than 1 adaptive class found: "
                    + cachedAdaptiveClass.getClass().getName()
                    + ", " + clazz.getClass().getName());
        }
    } else {
        try {
     		// Entering this branch represents the Wrapper decorative extension class, which has one feature: Contains
     		// A parameterized constructor, if not, throws an exception and enters another branch.
     		// The role of the Wrapper class will be discussed later
            clazz.getConstructor(type);
            // Cache Wrapper into cachedWrapperClasses
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } catch (NoSuchMethodException e) {
        	// Entering this branch represents a generic extension class
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
            	// For historical reasons, Dubbo was not initially configured as K-V in its configuration file
            	// Extension points are specified by the @Extension annotation, so here they are
            	// This comment gets the name
                name = findAnnotationName(clazz);
                // Because @Extension is obsolete, there are still non-K-V configurations in the configuration file.
                // So here's a simple name directly from the class name
                if (name == null || name.length() == 0) {
                    if (clazz.getSimpleName().length() > type.getSimpleName().length()
                            && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                        name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                    } else {
                        throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                    }
                }
            }
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                // Determines if the current extension point is labeled with the @Activate annotation, which indicates
                // This extension point is automatically activated
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (! cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }
}

So far, we've seen the entire process of Dubbo SPI implementation, and we've seen how Dubbo's powerful extensibility is implemented. But with so many extensions, how does Dubbo decide which extension point's method to call at runtime? This is another powerful mechanism of Dubbo: adaptive scaling. (PS: Note here the assignment of two variables, cachedAdaptiveClass and cachedWrapperClasses, which you will use later.)

2. Adaptive Extension Mechanism

What is adaptive extension? As mentioned earlier, there are many extension classes in Dubbo. These extension classes cannot be initialized all at first, which is very resource intensive, so we should initialize them when we use them, that is, lazy loading. However, this is contradictory. If the extension is not loaded, then the extension method cannot be invoked (except for static methods). If the extension method is not invoked, the extension cannot be loaded (in the official web language). So there is also an adaptive extension mechanism, so what is the principle? First, you need to understand the @Adaptive annotation, which can be labeled on classes and methods:

  • Labeled on a class to indicate that it is a custom adapter class

  • Labeling the method indicates the need to dynamically create an adapter class for the method

When a method of an extension class is called somewhere, the method of the adapter class is called first, then the adapter class calls the getExtension method based on the extension name to get the corresponding extension class object, and then the method of the object is called. The process is so simple, let's see how the code works. First, let's go back to the Extension Loader construction method:

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

It calls the getAdaptiveExtension method, which, as you can easily see from the method name, is to get an adapter class object:

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
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;
}

The simple method is to get the adapter class object from the cache, call the createAdaptiveExtension method to load the adapter class and create the object through reflection without getting it:

private T createAdaptiveExtension() {
    try {
    	// There's something injected here, skip it first
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

Call getAdaptiveExtensionClass to load the adapter class:

private Class<?> getAdaptiveExtensionClass() {
	// Loading configuration classes from configuration files is just analyzed here
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

The variable cachedAdaptiveClass should not be forgotten. It is assigned in the loadFile, our custom adapter extension class, or, if not, we call createAdaptiveExtensionClass to create dynamically:

private Class<?> createAdaptiveExtensionClass() {
	// Generate Java code for the adapter class, mainly implementing method logic labeled @Adaptive
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    // Call compiler compilation
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

This method is to generate the byte code of the adapter class. You must be curious about how the code of the adapter class is. Just breakpoints are needed to see it. Here we take the adapter class of the Protocol class as an example:

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.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
        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);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {
        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);
    }
}

The Protocol extension classes are exposed through the export method, the refer method refers to the service, and both methods are labeled with the @Adaptive annotation in the interface. So Dubbo generates dynamic adapter classes for it (this works a little like Java's dynamic proxy), and we see that both methods use the getExtension method to get an instance of the specified extension class (the extension class name comes from Invoker) (Later on) URL in because Dubbo is url-driven and all configurations are in url. This is how Dubbo's powerful adaptive extension mechanism works and we can apply it to our projects, which is good to see the source code. However, there's another problem, you must have wondered what compiler is just in the createAdaptiveExtension Class method. It is also a call to getAdaptiveExtension to get the adapter class. Doesn't this enter a dead loop? Of course not. First, let's go to the configuration file and see what Compiler's extension classes are:

adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

There is an AdaptiveCompiler class that we can guess by name is a custom adapter class, and then we can see the @Adaptive annotation on it to validate our guess. So we also mentioned above that in the loadFile method this class is assigned to the cachedAdaptiveClass variable cache. Then you get and instantiate the object in the createAdaptiveExtension -> getAdaptiveExtension Class method, so it doesn't loop endlessly, so what is done in that class?

public class AdaptiveCompiler implements Compiler {

	// Where is this assigned?
    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
        	// According to DEFAULT_COMPILER name acquisition
            compiler = loader.getExtension(name);
        } else {
			// Gets the default extension specified by the @SPI annotation value
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}

The adapter class gets the extension class from two places. First, look at getDefaultExtension:

public T getDefaultExtension() {
   		getExtensionClasses();
       if(null == cachedDefaultName || cachedDefaultName.length() == 0
               || "true".equals(cachedDefaultName)) {
           return null;
       }
       return getExtension(cachedDefaultName);
}

Not unfamiliar with cachedDefaultName, the value assigned in the loadExtensionClasses method is @SPI, which is javassist. Looking at the other branch, it's through DEFAULT_COMPILER value to get, this variable provides a setter method, which we can see in the past was called by the setCompiler method in the ApplicationConfig class, because this class is an instance of the configuration class, that is, the compiler type can be configured through the dubbo:application compiler parameter, the documentation can be viewed, and this configuration parameter does exist. So looking at the source code can help us understand the meaning of configuring parameters in a normal project, so that we can select and configure appropriate parameters, instead of simply copying the document.

3. Dubbo IOC

In the previous section, we see a method called injectExtension. What does it do? Next, we will analyze its role and implementation in detail.

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;
}



# summary

When it comes to interviews, in fact, it means brushing your head and brushing your head every day.

In order to prepare this "Gold, Three, Silver and Four" spring solicitation, brush one month's questions wildly, and fill in excessive knowledge of vulnerabilities, such as the algorithm, database, etc. for this delegation interview, Redis,I've brushed through topics like design patterns

**And I also sorted out all the questions I brushed myself PDF perhaps Word Document (with detailed answer parsing),[Friends in need can stamp here and get it for free](https://gitee.com/vip204888/java-p7)**

![My Mei Tuan offer Is it cold? Development Engineer ( Java Post) Notification of end on three sides...](https://img-blog.csdnimg.cn/img_convert/770d2ba0dacfa221245720f94143747b.png)

66 individual Java Interview Points of Knowledge

**Architecture topics ( MySQL,Java,Redis,Threads, Concurrency, Design Mode, Nginx,Linux,Framework, Micro Services, etc.)+Detailed explanation of interview questions for big factories (Baidu, Ali, Tencent, Huawei, Xunlei, NetEase, ZTE, Beijing Zhongsoft, etc.)**

![My Mei Tuan offer Is it cold? Development Engineer ( Java Post) Notification of end on three sides...](https://img-blog.csdnimg.cn/img_convert/4b2512d8637836fb6581d4c9134d0acc.png)

**Algorithmic brush ( PDF)**


}



# summary

When it comes to interviews, in fact, it means brushing your head and brushing your head every day.

In order to prepare this "Gold, Three, Silver and Four" spring solicitation, brush one month's questions wildly, and fill in excessive knowledge of vulnerabilities, such as the algorithm, database, etc. for this delegation interview, Redis,I've brushed through topics like design patterns

**And I also sorted out all the questions I brushed myself PDF perhaps Word Document (with detailed answer parsing),[Friends in need can stamp here and get it for free](https://gitee.com/vip204888/java-p7)**

[Outer Chain Picture Transfer in Progress...(img-o0kEbQbM-1628483901746)]

66 individual Java Interview Points of Knowledge

**Architecture topics ( MySQL,Java,Redis,Threads, Concurrency, Design Mode, Nginx,Linux,Framework, Micro Services, etc.)+Detailed explanation of interview questions for big factories (Baidu, Ali, Tencent, Huawei, Xunlei, NetEase, ZTE, Beijing Zhongsoft, etc.)**

[Outer Chain Picture Transfer in Progress...(img-A2jDJGih-1628483901748)]

**Algorithmic brush ( PDF)**

![My Mei Tuan offer Is it cold? Development Engineer ( Java Post) Notification of end on three sides...](https://img-blog.csdnimg.cn/img_convert/2ba1bf4a59eb3579867ebfb6dafbdc0c.png)

Keywords: Java Back-end Interview Programmer

Added by Pr0digy on Wed, 05 Jan 2022 04:18:41 +0200