Class loader, parent delegation, SPI mechanism in Java

Reference:
Java Parental Delegation Model: Why Parental Delegation? How to break it? Where is it broken?
[Code Pipixia] takes you to take stock of the parental delegation mechanism [principles, advantages and disadvantages], and how to break it?
Among the three SPI mechanisms of JDK/Dubbo/Spring, which one is better?


Preface

I rarely come into contact with class loaders when doing business development, but if you want to learn more about open source projects such as Tomcat and Spring, or engage in the development of underlying architecture, it is essential to understand or even be familiar with the principles of class loading.

What are the class loaders for Java? What is Parental Delegation? Why parent delegation? How to break it? I have some understanding of these concepts, and I even memorized these knowledge points for interviews, but when I go into more details, I know very little.


1. Class loader

Class loader, as the name suggests, is a tool that can load Java bytecode into java.lang.Class instances. This process includes reading byte arrays, verification, parsing, initialization, etc. Additionally, it can also load resources, including image files and configuration files.

Class loader features:

  • Dynamic loading does not need to be loaded when the program starts running. Instead, it is loaded dynamically on demand while the program is running. There are many sources of bytecode, such as compressed jars, wars, the network, local files, etc. The dynamic loading feature of the class loader provides strong support for hot deployment and hot loading.
  • Fully responsible, when a class loader loads a class, all other classes that this class depends on and references are loaded by this class loader, unless another class loader is explicitly specified in the program to load. So breaking parent delegation cannot break the order above the extension class loader.

The uniqueness of a class is determined by the class loader that loads it and the class itself (the fully qualified name of the class + the instance ID of the class loader as the unique identifier). Comparing whether two classes are equal (including Class objects ​equals(), ​​isAssignableFrom(), ​​isInstance()​​​and ​​instanceof​​keywords, etc.) is meaningful only if the two classes are loaded by the same class loader. Otherwise, even if These two classes originate from the same Class file and are loaded by the same virtual machine. As long as the class loaders that load them are different, the two classes must not be equal.

In terms of implementation, class loaders can be divided into two types: one is the startup class loader , which is implemented in the C++ language and is part of the virtual machine itself; the other is the class loader inherited java.lang.ClassLoader​​from , including extension classes loaders , application class loaders , and custom class loaders .

  • Bootstrap ClassLoader (Bootstrap ClassLoader): Responsible for loading the path ​​<JAVA_HOME>\libin the directory or ​​-Xbootclasspaththe path specified by the parameter, and is recognized by the virtual machine (only recognized by the file name, such as rt.jar , a class library with an inconsistent name will not be loaded even if it is placed in the lib directory) The class library is loaded into the virtual machine memory. The startup class loader cannot be directly referenced by the Java program. When writing a custom class loader, if the user wants to set the Bootstrap ClassLoader as its parent, he can set null directly.

  • Extension ClassLoader: Responsible for loading all class libraries ​​<JAVA_HOME>\lib\ext​​​in the directory or java.ext.dirsin the path specified by the system variable. This class loader is ​​sun.misc.Launcher$ExtClassLoader​​​implemented by. The extension class loader is loaded by the startup class loader, and its parent class loader is the startup class loader, that is, parent=null​​.

  • Application ClassLoader: Responsible for loading the class library specified on the user class path (ClassPath), ​​sun.misc.Launcher$App-ClassLoaderimplemented by . Developers can directly obtain the application class loader through the method ​​java.lang.ClassLoader​​​in , so it can also be called the system class loader. ​​getSystemClassLoader()​​The application class loader is also loaded by the startup class loader, but its parent class loader is the extension class loader. In an application, the system class loader is usually the default class loader.


2. Parental delegation mechanism

1. Introduction to the parent delegation mechanism

The JVM does not load all the .class files when it starts, but only loads the class when the program uses it during running . Except for the startup class loader, all other class loaders need to inherit the abstract class ClassLoader. This abstract class defines three key methods. It is very important to understand their functions and relationships.

public abstract class ClassLoader {
    
    
    //每个类加载器都有个父加载器
    private final ClassLoader parent;

    public Class<?> loadClass(String name) {
    
    
        //查找一下这个类是不是已经加载过了
        Class<?> c = findLoadedClass(name);

        //如果没有加载过
        if (c == null) {
    
    
          //先委派给父加载器去加载,注意这是个递归调用
          if (parent != null) {
    
    
              c = parent.loadClass(name);
          } else {
    
    
              // 如果父加载器为空,查找Bootstrap加载器是不是加载过了
              c = findBootstrapClassOrNull(name);
          }
        }
        // 如果父加载器没加载成功,调用自己的findClass去加载
        if (c == null) {
    
    
            c = findClass(name);
        }

        return c;
    }

    protected Class<?> findClass(String name){
    
    
        // 1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存
        // ...

        // 2. 调用defineClass将字节数组转成Class对象
        return defineClass(buf, off, len)}

    // 将字节码数组解析成一个Class对象,用native方法实现
    protected final Class<?> defineClass(byte[] b, int off, int len){
    
    
        // ...
    }
}

Several key information can be obtained from the above code:

  1. JVM class loaders are hierarchical, and they have a parent-child relationship. This relationship is not inheritance and maintenance, but combination. Each class loader holds a parent field that points to the parent loader.
  2. defineClass​The responsibility of the method is to call the native method to parse the bytecode of the Java class into a Class object.
  3. findClass​​​The main responsibility of the method is to find the .class file and read the .class file into memory to obtain the bytecode array, and then call defineClassthe method to obtain the Class object. Subclasses must implement findClass​​​​.
  4. loadClass​​The main responsibility of the method is to implement the parent delegation mechanism: first check whether this class has been loaded, and return directly if it has been loaded, otherwise it is delegated to the parent loader for loading. This is a recursive call, delegated upward layer by layer . When the top-level class loader (startup class loader) cannot load the class, it is delegated to sub-class loaders layer by layer .

Insert image description here

2. The role of the parental delegation mechanism

Parental delegation ensures that class loaders, bottom-up delegation, and top-down loading ensure that each class is the same class in each class loader.

A very obvious purpose: to ensure the loading security of ​​java​​​official class libraries ​​<JAVA_HOME>\lib​​​and extended class libraries ​​<JAVA_HOME>\lib\ext​​and not be overwritten by developers. For example ​​java.lang.Object​​​, a class is stored in it ​​rt.jar​​. No matter which class loader wants to load this class, it will eventually be delegated to the startup class loader for loading. Therefore, the Object class is the same class in various class loader environments of the program.

If developers develop their own open source framework, they can also customize the class loader and use the parent delegation model to protect the classes that need to be loaded by their own framework from being overwritten by the application.

Its advantages are summarized as follows:

  1. Avoid repeated loading of classes
  2. Protect program security and prevent core APIs from being tampered with at will

Disadvantages : In some scenarios, the parental delegation system is too restrictive, so sometimes it is necessary to break the parental delegation mechanism to achieve the goal. For example: SPI mechanism

3. Limitations of Parental Delegation and Thread Context Class Loaders

Take JDBC to create a database connection as an example to introduce the limitations of the parent delegation mechanism:

When executing the following code to create a connection, DriverManagerthe class needs to be initialized

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "root");

When initializing DriverManagera class, the following line of code will be executed to load classpathall the following Driverimplementation classes that implement the interface:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

Then here comes the problem: the class
located rt.jarbelow java.sql.DriverManageris loaded by the startup class loader, so when the above code is encountered during loading, it will try to load all Driver implementation classes (SPI), but these implementation classes are basically third-party Provided, third-party classes cannot be loaded by the startup class loader.

How to solve this problem? JDBC breaks the principle of parental delegation by using the application class loader by
introducing (thread context loader, which is AppClassLoader by default).ThreadContextClassLoaderAppClassLoader

ServiceLoader.load()accomplish:

public static <S> ServiceLoader<S> load(Class<S> service) {
    
    
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

The thread context class loader is actually a class loader delivery mechanism. You can set a context class loader for a thread through the java.lang.Thread#setContextClassLoader method. This class loader can be retrieved during subsequent execution of the thread (java.lang.Thread#getContextClassLoader) ​) out for use.

If the context class loader is not set when creating a thread, it will be obtained from the parent thread (parent = currentThread()). If it is not set in the global scope of the application, the application class loader will be loaded by default. device.

The thread context class loader appears to facilitate the destruction of parental delegation:

A typical example is the JNDI service. JNDI is now a standard service in Java. Its code is loaded by the startup class loader (rt.jar put in JDK 1.3), but the purpose of JNDI is to centralize resources. For management and search, it needs to call the code of the JNDI interface provider (SPI, Service Provider Interface) implemented by an independent vendor and deployed under the ClassPath of the application, but it is impossible for the startup class loader to load classes under the ClassPath.

But with the thread context class loader, it is easier to handle. The JNDI service uses the thread context class loader to load the required SPI code. That is, the parent class loader requests the child class loader to complete the class loading action. This behavior In fact, it opens up the hierarchical structure of the parent delegation model to reversely use the class loader. In fact, it violates the general principles of the parent delegation model, but there is nothing you can do about it.

All loading actions involving SPI in Java basically use this method, such as JNDI, JDBC, JCE, JAXB and JBI, etc.

Excerpted from "In-depth Understanding of Java Virtual Machine" Zhou Zhiming

Example of Tomcat breaking the parental delegation mechanism:

Tomcat is a web container, so a web container may need to deploy multiple applications. Different applications may rely on different versions of the same third-party class library, but the full path name of a class in different versions of the class library may be the same.

If the default parent-delegated class loading mechanism is used, multiple identical classes cannot be loaded. Therefore, Tomcat destroys the principle of parental delegation, provides an isolation mechanism, and provides a separate WebAppClassLoader loader for each web container.

Tomcat's class loading mechanism: In order to achieve isolation, classes defined by the Web application are loaded first. Therefore, the parent delegation agreement is not followed. Each application has its own class loader - WebAppClassLoader, which is responsible for loading the class files in its own directory. When the time comes, it will be handed over to CommonClassLoader for loading, which is exactly the opposite of parental delegation.

4. How the parental delegation mechanism is broken

There are two ways in which the parental delegation mechanism is broken:

  1. Thread context class loader. Use ThreadContextClassLoader to load classes that cannot be loaded by the superior class loader (mentioned in JDBC creation of database connection above)
  2. Custom class loader. Let’s introduce this method to destroy the parental delegation mechanism.

If you want to customize the class loader, you need to inherit ClassLoader and rewrite it ​​findClass​​​. If you want to destroy the class loading order delegated by parents, you need to rewrite it​​loadClass​​​ . Here is a custom class loader that overrides loadClass to break parental delegation:

package co.dreampointer.test.classloader;

import java.io.*;

public class MyClassLoader extends ClassLoader {
    
    
    public MyClassLoader(ClassLoader parent) {
    
    
        super(parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    
    
        // 1.找到ExtClassLoader,并首先委派给它加载
        // 为什么?
        // 双亲委派的破坏只能发生在"AppClassLoader"及其以下的加载委派顺序,"ExtClassLoader"上面的双亲委派是不能破坏的!
        // 因为任何类都是继承自超类"java.lang.Object",而加载一个类时,也会加载继承的类,如果该类中还引用了其他类,则按需加载,且类加载器都是加载当前类的类加载器。
        ClassLoader classLoader = getSystemClassLoader();
        while (classLoader.getParent() != null) {
    
    
            classLoader = classLoader.getParent();
        }
        Class<?> clazz = null;
        try {
    
    
            clazz = classLoader.loadClass(name);
        } catch (ClassNotFoundException ignored) {
    
    
        }
        if (clazz != null) {
    
    
            return clazz;
        }

        // 2.自己加载
        clazz = this.findClass(name);
        if (clazz != null) {
    
    
            return clazz;
        }

        // 3.自己加载不了,再调用父类loadClass,保持双亲委派模式
        return super.loadClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        // 1.获取class文件二进制字节数组
        byte[] data;
        try {
    
    
            ByteArrayOutputStream baOS = new ByteArrayOutputStream();
            // 加载class文件内容到字节数组
            FileInputStream fIS = new FileInputStream("test/target/classes/co/dreampointer/test/classloader/MyTarget.class");
            byte[] bytes = new byte[1024];

            int len;
            while ((len = fIS.read(bytes)) != -1) {
    
    
                baOS.write(bytes, 0, len);
            }
            data = baOS.toByteArray();
        } catch (IOException e) {
    
    
            e.printStackTrace();
            return null;
        }

        // 2.字节码数组加载到 JVM 的方法区,
        // 并在 JVM 的堆区建立一个java.lang.Class对象的实例
        // 用来封装 Java 类相关的数据和方法
        return this.defineClass(name, data, 0, data.length);
    }
}

Insert image description here

test program:

package co.dreampointer.test.classloader;

public class Main {
    
    
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        // 初始化MyClassLoader
        // 将加载MyClassLoader类的类加载器设置为MyClassLoader的parent
        MyClassLoader myClassLoader = new MyClassLoader(MyClassLoader.class.getClassLoader());

        System.out.println("MyClassLoader的父类加载器:" + myClassLoader.getParent());
        // 加载 MyTarget
        Class<MyTarget> clazz = (Class<MyTarget>) myClassLoader.loadClass("co.dreampointer.test.classloader.MyTarget");
        System.out.println("MyTarget的类加载器:" + clazz.getClassLoader());
    }
}

//控制台打印
MyClassLoader的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
MyTarget的类加载器:co.dreampointer.test.classloader.MyClassLoader@5b1d2887

Pay attention to the position of destroying parent delegation : the custom class loading mechanism is first delegated to ExtClassLoader for loading, and ExtClassLoader is then delegated to BootstrapClassLoader. If neither can be loaded, then customize the class loader MyClassLoader loads. If MyClassLoader cannot load, it is handed over to AppClassLoader. Why can't we just let the custom class loader load it directly?

Because the destruction of parental delegation can only occur in the loading delegation sequence of AppClassLoader and below, the parental delegation above ExtClassLoader cannot be destroyed!

Root cause: Any class inherits from the super class java.lang.Object. When loading a class, the inherited class will also be loaded. If other classes are also referenced in the class, they will be loaded on demand, and Class loaders are all class loaders that load the current class.

For example, the MyTarget class only implicitly inherits Object. If the custom class loader MyClassLoader loads MyTarget, it will also load Object. If loadClass directly calls findClass of MyClassLoader, an error java.lang.SecurityException: Prohibited package name: java.lang will be reported.

For security reasons, Java does not allow class loaders other than BootStrapClassLOader to load class libraries in the official Java. directory. In the defineClass source code, the native method defineClass1 will eventually be called to obtain the Class object. Before this, it will be checked whether the fully qualified name of the class is Starting with java.​​​. (If you want to completely bypass Java's class loading, you need to implement defineClass yourself. However, due to my limited personal ability, I have not studied the rewriting of defineClass in depth, and generally it will not destroy ExtClassLoader. ​​The above parental delegation, unless java is no longer used)

defineClass source code is as follows:

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    
    
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}
private ProtectionDomain preDefineClass(String name,
                                        ProtectionDomain pd)
{
    
    
    if (!checkName(name))
        throw new NoClassDefFoundError("IllegalName: " + name);

    // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
    // relies on the fact that spoofing is impossible if a class has a name
    // of the form "java.*"
    if ((name != null) && name.startsWith("java.")) {
    
    
        throw new SecurityException
            ("Prohibited package name: " +
             name.substring(0, name.lastIndexOf('.')));
    }
    if (pd == null) {
    
    
        pd = defaultDomain;
    }

    if (name != null) checkCerts(name, pd.getCodeSource());

    return pd;
}

Cases of destroying parental delegation through custom class loaders are very common in daily development. For example, in Tomcatorder to achieve loading isolation between web applications, a class loader is customized. Each Context represents a web application and has a ​webappClassLoader​​. Another example is that the implementation of hot deployment and hot loading requires a custom class loader. The location of destruction is to skip AppClassLoader.

5. Summary

  1. Class loading in java is to obtain the binary bytecode array of the .class file and load it into the method area of ​​​​the JVM, and create a heap area in the JVM to encapsulate the data and methods related to the java class. java.lang.Class object instance.

  2. There are three class loaders in Java by default, startup class loader (BootstrapClassLoader), extension class loader (ExtClassLoader), and application class loader (also called system class loader) (AppClassLoader). There is a parent-child relationship between class loaders. This relationship is not an inheritance relationship, but a combination relationship. If parent=null, its parent is the startup class loader. The startup class loader cannot be directly referenced by the Java program.

  3. Parental delegation is the hierarchical relationship between class loaders. The process of loading a class is a recursive calling process. First, the parent class loader is delegated layer by layer to load until it reaches the top-level startup class loader. The startup class loader cannot When loading, it is delegated layer by layer to the subclass loader for loading.

  4. The purpose of parent delegation is mainly to ensure the loading security of the official java class library <JAVA_HOME>\lib and extended class library <JAVA_HOME>\lib\ext. Overridden by developers.

  5. There are two ways to destroy parent delegation: the first is a custom class loader, which must override findClass and loadClass; the second is through the transitivity of the thread context class loader, letting the parent The class loader calls the loading action of the subclass loader.


3. SPI mechanism

SPI stands for Service Provider Interface and is a service discovery mechanism. The essence of SPI is to configure the fully qualified name of the interface implementation class in a file, and the service loader reads the configuration file and loads the implementation class. This allows you to dynamically replace implementation classes for interfaces at runtime. Because of this feature, we can easily provide extended functions for our programs through the SPI mechanism.

1. JDK SPI

The core class of the SPI function provided in the JDK is java.util.ServiceLoader. The function is to obtain multiple configuration implementation files under "META-INF/services/" through the class name.

Example: javax.servlet.ServletContainerInitializerThe content of the file is the fully qualified class name of the implementation class of the interface represented by the file name.org.springframework.web.SpringServletContainerInitializer
Insert image description here

Since the loading order (classpath) is specified by the user, whether we load the first or the last one, it is possible that the user-defined configuration cannot be loaded.

So this is also a disadvantage of the JDK SPI mechanism. It is impossible to confirm which implementation is loaded, and it is also impossible to load a specified implementation. Relying solely on the order of ClassPath is a very imprecise method.

2. Spring SPI

Spring's SPI configuration file is a fixed file META-INF/spring.factories, similar in function to JDK. Each interface can have multiple extension implementations, and it is very simple to use:

// Get the LoggingSystemFactory configured in all factories files

List<LoggingSystemFactory>> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);

The following is a configuration of spring.factories in SpringBoot

# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver

In Spring SPI, all configurations are put into a fixed file, eliminating the trouble of configuring a lot of files. As for the extended configuration of multiple interfaces, whether it is better to use one file or each separate file is a matter of opinion. (I personally like Spring, which is clean and neat).

Although Spring's SPI belongs to spring-framework (core), it is currently mainly used in SpringBoot. Like the previous two SPI mechanisms, Spring also supports the existence of multiple spring.factories files in ClassPath. When loading, these spring.factories files will be loaded sequentially in the order of the classpath and added to an ArrayList. Since there are no aliases, there is no concept of duplication removal. Just add as many as there are.

However, since Spring's SPI is mainly used in Spring Boot, the ClassLoader in Spring Boot will give priority to loading files in the project instead of files in the dependency package. So if you define a spring.factories file in your project, then the files in your project will be loaded first, and among the obtained Factories, the implementation class configured in spring.factories in the project will also be ranked first.

If we want to extend an interface, we only need to create a new META-INF/spring.factoriesfile in your project (SpringBoot) and add the configuration you want.

For example, if I just want to add a new LoggingSystemFactory implementation, then I only need to create a new META-INF/spring.factories file instead of complete copying and modification:

org.springframework.boot.logging.LoggingSystemFactory=\
com.example.log4j2demo.Log4J2LoggingSystem.Factory

3. Dubbo SPI

Dubbo loads all components through the SPI mechanism. However, Dubbo does not use Java's native SPI mechanism, but enhances it to better meet the needs. In Dubbo, SPI is a very important module. Based on SPI, we can easily expand Dubbo. If you want to learn the source code of Dubbo, you must understand the SPI mechanism. Next, let's first understand the usage of Java SPI and Dubbo SPI, and then analyze the source code of Dubbo SPI.
A new SPI mechanism is implemented in Dubbo, which is more powerful and more complex. The relevant logic is encapsulated in the ExtensionLoader class. Through ExtensionLoader, we can load the specified implementation class. The configuration files required by Dubbo SPI need to be placed in the META-INF/dubbo path. The configuration content is as follows (the following demo is from dubbo official documentation).

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

Different from Java SPI implementation class configuration, Dubbo SPI is configured through key-value pairs, so that we can load the specified implementation class on demand. In addition, the @SPI annotation needs to be marked on the interface when using it. Let's demonstrate the usage of Dubbo SPI:

@SPI
public interface Robot {
    
    
    void sayHello();
}

public class OptimusPrime implements Robot {
    
    
    @Override
    public void sayHello() {
    
    
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {
    
    
    @Override
    public void sayHello() {
    
    
        System.out.println("Hello, I am Bumblebee.");
    }
}

public class DubboSPITest {
    
    
    @Test
    public void sayHello() throws Exception {
    
    
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

The biggest difference between Dubbo SPI and JDK SPI is that it supports "aliases" . You can obtain a fixed extension point through the alias of an extension point. Just like in the above example, I can get the implementation with the alias "optimusPrime" among Robot's multiple SPI implementations, and I can also get the implementation with the alias "bumblebee". This function is very useful!

Through the value attribute of the @SPI annotation, you can also default to an "alias" implementation. For example, in Dubbo, the default is Dubbo private protocol: dubbo protocol - dubbo://
**
Let’s take a look at the interface of the protocol in Dubbo:

@SPI("dubbo")
public interface Protocol {
    
    
    // ...
}

On the Protocol interface, a @SPI annotation is added, and the value of the annotation is dubbo. When obtaining the implementation through SPI, the implementation with the alias dubbo in the Protocol SPI configuration will be obtained. The com.alibaba.dubbo.rpc.Protocol file is as follows :

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

Then you only need to get the extension implementation corresponding to the value on the @SPI annotation through getDefaultExtension.

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension();
//protocol: DubboProtocol

There is also an Adaptive mechanism. Although it is very flexible, its usage is not very "elegant" and I will not introduce it here.

There is also a "loading priority" in Dubbo's SPI. The built-in (internal) is loaded first, and then the external (external) is loaded in order of priority. If duplicates are encountered, they will be skipped and will not be loaded.

Therefore, if you want to rely on classpath loading order to overwrite built-in extensions, it is also an unreasonable approach. The reason is the same as above - the loading order is not strict.

4. Comparison

Comparing the three SPI mechanisms, JDK's built-in mechanism is the weakest, but because it is built-in in JDK, it still has certain application scenarios. After all, no additional dependencies are needed; Dubbo has the richest functions, but the mechanism is a bit complicated and only It can be used with Dubbo and cannot be completely regarded as an independent module; the functions of Spring are almost the same as those of JDK. The biggest difference is that all extension points are written in a spring.factories file, which is also an improvement, and IDEA perfectly supports syntax prompts.

JDK SPI DUBBO SPI Spring SPI
File mode A separate file for each extension point A separate file for each extension point All extension points in one file
Get a fixed implementation Not supported, you can only get all implementations in order There is the concept of "alias". You can get a fixed implementation of the extension point through the name. It is very convenient to cooperate with Dubbo SPI annotations. Not supported, all implementations can only be obtained in order. However, since Spring Boot ClassLoader will give priority to loading files in user code, it can ensure that the user-defined spring.factoires file is first, and the customized extension can be obtained fixedly by obtaining the first factory.
other none Supports dependency injection inside Dubbo, distinguishes Dubbo's built-in SPI and external SPI through directories, and loads the internal ones first to ensure that the internal ones have the highest priority. none

Guess you like

Origin blog.csdn.net/qq_45867699/article/details/132100088