JDK SPI 、Spring SPI、Dubbo SPI机制

JDK SPI机制

 SPI(Service Provider Interface),是一种将服务接口与服务实现分离以达到解耦可拔插、大大提升了程序可扩展性的机制。

约定(我觉得称之为规范更合适):

1. 制定统一的规范(比如 java.sql.Driver

2. 服务提供商提供这个规范具体的实现,在自己jar包的META-INF/services/目录里创建一个以服务接口命名的文件,内容是实现类的全命名(比如:com.mysql.jdbc.Driver)。

3. 平台引入外部模块的时候,就能通过该jar包META-INF/services/目录下的配置文件找到该规范具体的实现类名,然后装载实例化,完成该模块的注入。

这个机制最大的优点就是无须在代码里指定,进而避免了代码污染,实现了模块的可拔插。

 JDK SPI的一个典型案例就是 java.sql.Driver

 

 我们最熟悉的代码

// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("url", "user", "password");

我们进入DriverManager类,里面有个静态代码块,这部分的内容会先执行。

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

加载并初始化驱动

private static void loadInitialDrivers() {
    // ...

    // 如果驱动被打包作为服务提供者,则加载它。
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // 1. load
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // 2. 获取Loader的迭代器
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                // 3. 调用next方法
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });
    // ...
}

ServiceLoader#load方法

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // load
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
    // new 一个 ServiceLoader,参数为服务接口Class和类加载器
    return new ServiceLoader<>(service, loader);
}

可以看出,上面主要是获取类加载器新建ServiceLoader的过程,没有加载实现类的动作。现在看:ServiceLoader#iterator 方法

public Iterator<S> iterator() {
    // 这里是个Iterator的匿名内部类,重写了一些方法
    return new Iterator<S>() {

        // 已存在的提供者
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            // 先检查缓存
            if (knownProviders.hasNext())
                return true;
            // 缓存没有,走 java.util.ServiceLoader.LazyIterator#hasNext 方法
            return lookupIterator.hasNext();
        }

        public S next() {
            // 同样先检查缓存
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            // 缓存没有,走 java.util.ServiceLoader.LazyIterator#next 方法
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

看 ServiceLoader.LazyIterator#hasNext 方法

// java.util.ServiceLoader.LazyIterator#hasNext
public boolean hasNext() {
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            // 获取全路径:META-INF/services/java.sql.Driver
            String fullName = PREFIX + service.getName();
            // 加载资源
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    // 这里负责解析前面加载的配置信息
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        // 解析的返回值是一个 Iterator<String> 类型,其中的String代表文件里配置的实现类全限定名,比如:com.mysql.jdbc.Driver
        pending = parse(service, configs.nextElement());
    }
    // 把nextName初始化
    nextName = pending.next();
    return true;
}

ServiceLoader.LazyIterator#next 方法

// java.util.ServiceLoader.LazyIterator#next
public S next() {
    if (acc == null) {
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
// 进入 java.util.ServiceLoader.LazyIterator#nextService 方法
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // com.mysql.jdbc.Driver
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        // 加载 com.mysql.jdbc.Driver Class
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        // 实例化并转换成 com.mysql.jdbc.Driver 对象
        S p = service.cast(c.newInstance());
        // 添加到缓存
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

所以,DriverManager做了什么事:

// 1. 获取类加载器并创建ServiceLoader对象
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 2. 创建迭代器并覆盖hasNext和next方法
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
    // 3. 这个方法主要是读取配置文件,获取其中实现类的全限定名。
    while(driversIterator.hasNext()) {
        // 4. 这个方法主要是根据全限定名去生成一个实例
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}

另外你也能发现,ServiceLoader只提供了遍历的方式来获取目标实现类,没有提供按需加载的方法,这也是常说的不足之处。

自己实现

定义一个接口

package com.demo.spi;

public interface Funny {

    void deal();
}

写一个实现类

package com.demo.spi;

public class FunnyImpl implements Funny {
    @Override
    public void deal() {
        System.out.println("FunnyImpl");
    }
}

配置

文件内容

 执行测试

@Test
public void test(){
    ServiceLoader<Funny> serviceLoader = ServiceLoader.load(Funny.class);
    serviceLoader.forEach(Funny::deal);
}

输出

Spring SPI机制

对于Spring的SPI机制主要体现在SpringBoot中。我们知道SpringBoot的启动包含new SpringApplication执行run方法两个过程,new的时候有这么个逻辑:(getSpringFactoriesInstances

这个方法走到里面,无非 1. 加载类的全限定名列表。2. 根据类名通过反射实例化。

重点在于:SpringFactoriesLoader.loadFactoryNames(type, classLoader) 

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    // 获取类的全限定名
    String factoryClassName = factoryClass.getName();
    // 1. 执行loadSpringFactories,这里只传入了类加载器,肯定是要获取全部配置
    // 2. getOrDefault,获取指定接口的实现类名称列表,如果没有则返回一个空列表
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 先检查缓存
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
    try {
        // 获取类路径下所有META-INF/spring.factories
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 把加载的配置转换成Map<String, List<String>>格式
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

它还提供了实例化的方法:SpringFactoriesLoader.loadFactories(factoryClass, classLoader) 利用反射实现,理解起来也不困难,不做解释了

对比ServiceLoader:

1. 都是XXXLoader。命名格式都一样。

2. 一个是加载 META-INF/services/ 目录下的配置;一个是加载 META-INF/spring.factories 固定文件的配置。思路都一样。

3. 两个都是利用ClassLoader和ClassName来完成操作的。不同的是Java的ServiceLoader加载配置和实例化都是自己来实现,并且不能按需加载;SpringFactoriesLoader既可以单独加载配置然后按需实例化也可以实例化全部。

如何实现一个Spring-Boot-Starter?

先看一下SpringBoot的相关要点。

这个是SpringBoot的 @SpringBootApplication 注解,里面还有一个 @EnableAutoConfiguration 注解,开启自动配置的。

可以发现这个自动配置注解在另一个工程,而这个工程里也有个spring.factories文件,如下图:

我们知道在SpringBoot的目录下有个spring.factories文件,里面都是一些接口的具体实现类,可以由SpringFactoriesLoader加载。

那么同样的道理,spring-boot-autoconfigure模块也能动态加载了。看一下其中的内容:

我们看到有个接口【org.springframework.boot.autoconfigure.EnableAutoConfiguration】,有N个配置类,都是自动配置相关的,那么我们可以猜一猜,是不是模仿一下这样的配置,我们就可以丝滑进入了?

为了证明这一点,我们看一下 dubbo-spring-boot-starter 的结构

你会发现starter项目没有实现类,只有个pom文件。

它的关键在于引入了一个 autoconfigure 依赖。

这个里面配置了Spring的 spring.factories 其中只有一个配置

看到这里你是不是感觉到了什么:

1. 新建一个只有pom的starter工程,引入写好的自动配置模块。

2. 自动配置模块配置 spring.factories 文件,格式如上。

3. 具体实现自动配置逻辑。

这样当SpringBoot启动加载配置类的时候,就会把这些第三方的配置一起加载。

所以我认为 Starter的作用就是在启动之前把第三方的配置加载到容器中,具体表现就是无须用户手动配置即可使用。【如果不准确望指正

具体实现一个starter

两个maven工程,目录如下:

custom-spring-boot-autoconfigure

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>custom-spring-boot-autoconfigure</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
    </dependencies>

</project>

CustomAutoConfiguration

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
public class CustomAutoConfiguration {

    @Bean
    public CustomConfig customConfig(){
        System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!第三方自定义的配置");
        return new CustomConfig();
    }
}

CustomConfig

/**
 * 自定义配置
 */
public class CustomConfig {

    private String value = "Default";
}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.CustomAutoConfiguration

一共四个文件

custom-spring-boot-starter

只有pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>custom-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.demo</groupId>
            <artifactId>custom-spring-boot-autoconfigure</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

</project>

最后把这两个工程install

下面是新建一个SpringBoot项目,然后引入咱们自定义的starter依赖。

<dependency>
    <groupId>com.demo</groupId>
    <artifactId>custom-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

然后启动就可以啦,可以看见,用户引入依赖之后无需手动配置就可以使用第三方插件

看下工程依赖

Dubbo SPI机制

上面介绍了两种SPI,到Dubbo这里就不难理解了吧?

思路都是处理类+约定配置 ,在Dubbo里,约定扩展配置在  META-INF/dubbo/internal/ 目录下

在Dubbo源码里也可以发现,核心类是 ExtensionLoader

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

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

getExtensionLoader

// 这个方法就是获取一个特定类型的ExtensionLoader的实例
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 an interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    // 先从缓存中取
    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;
}

下面看getExtension方法

public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        // 获取默认的扩展类
        return getDefaultExtension();
    }
    // Holder用来持有目标对象
    final Holder<Object> holder = getOrCreateHolder(name);
    // 获取实例
    Object instance = holder.get();
    // 没有的话,双重检查并创建实例
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

看createExtension方法

private T createExtension(String name) {
    // 1. 获取扩展Class
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 先从缓存中获取
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 没有则新建
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 2. IOC注入依赖
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            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 + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

1. 获取所有扩展类

private Map<String, Class<?>> getExtensionClasses() {
    // 缓存 + 双重检查
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加载扩展类
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

// 对于loadDirectory,每个目录有两个执行逻辑,因为目前要兼容alibaba老版本
private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();
    Map<String, Class<?>> extensionClasses = new HashMap<>();
    // 加载目录:META-INF/dubbo/internal/
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    // 加载目录:META-INF/dubbo/
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    // 加载目录:META-INF/services/
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    // 文件路径,比如 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        ClassLoader classLoader = findClassLoader();
        // 加载文件
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 加载资源(读取文件内容)
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            // 按行读取
            while ((line = reader.readLine()) != null) {
                // 判断注释
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        // 按照 = 分割,前面是name,后面是类全限定名
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            // 加载Class
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                        exceptions.put(line, e);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                type + ", class line: " + clazz.getName() + "), class "
                + clazz.getName() + " is not subtype of interface.");
    }
    // 检测目标类上是否有 Adaptive 注解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // 设置 cachedAdaptiveClass缓存
        cacheAdaptiveClass(clazz);
    // 检测 clazz 是否是 Wrapper 类型
    } else if (isWrapperClass(clazz)) {
        // 存储 clazz 到 cachedWrapperClasses 缓存中
        cacheWrapperClass(clazz);
    } else {
        // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 存储 name 到 Activate 注解对象的映射关系
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 存储 Class 到名称的映射关系
                cacheName(clazz, n);
                // 存储名称到 Class 的映射关系
                saveInExtensionClass(extensionClasses, clazz, n);
            }
        }
    }
}

这部分有些注释直接拿的官网,总结下来这部分:

1. 根据约定的路径比如【META-INF/dubbo/internal/】加载文件URl。

2. 读取并解析每一个文件内容,具体为按行读取,去掉注释,按等号分割为 name-class全限定名键值对。

3. 根据类全限定名生成Class对象,根据Class对象的的特点进行相关的缓存以及name到Class对象的缓存。

2. IOC注入依赖

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                // 这里判断方法是否是setter方法,Dubbo目前只处理setter的IOC
                if (isSetter(method)) {
                    // 如果标注了@DisableInject,则不进行注入
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    // 获取 setter 方法参数类型
                    Class<?> pt = method.getParameterTypes()[0];
                    // 基本类型跳过
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        // 获取属性名,比如 setName 方法对应属性名 name
                        String property = getSetterProperty(method);
                        // 从 ObjectFactory 中获取依赖对象
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 通过反射调用 setter 方法设置object依赖
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

这部分容易理解,核心部分利用反射实现,其它前置处理做了一些校验。

而使用Dubbo SPI的话也是一样的套路,扩展类实现 + 配置 + ExtensionLoader

猜你喜欢

转载自www.cnblogs.com/LUA123/p/12460869.html