目录

一、Java的SPI

二、Dubbo的SPI

 dubbo的IOC       

  dubbo的AOP

 动态编译


        SPI 全称为 Service Provider Interface,是一种服务发现机制。本质是由将接口的实现类的全限定名配置在文件当中,由服务加载,这样可以在运行时,动态的为接口加载实现类。

        举个例子,我们在用JDBC连接数据库时,创建连接就能直接获取到Mysql或者Oracle,Java时如何调用到相对应的驱动呢?

        我们将连接数据库看作一个扩展点,其他数据都是该点的实现,当我们需要连接相对应的的数据库时,Spi会帮助我们发现有哪些实现并加载。

一、Java的SPI

        按照惯例,先写一个例子,先定义一个接口EatFood,其次创建它的两个实现类,KFC和McDonald,然后META-INF/services下创建一个EatFood的全限定名的文件,里面存放着两个实现类的全限定名,然后我们可以在执行一下代码

public interface EatFood {
    void eat();
}
public class KFC implements EatFood{
    @Override
    public void eat() {
        System.out.println("吃肯德基");
    }
}
public class McDonald implements EatFood{
    @Override
    public void eat() {
        System.out.println("吃麦当劳");
    }
}
com.yxl.KFC
com.yxl.McDonald

测试代码

public class JavaSpiTest {
    @Test
    public void test(){
        ServiceLoader<EatFood> eatFoodServiceLoader = ServiceLoader.load(EatFood.class);
        Iterator<EatFood> iterator = eatFoodServiceLoader.iterator();
        while(iterator.hasNext()){
            EatFood eatFood = iterator.next();
            eatFood.eat();
        }
    }
}

很简单的一段代码,这时我们来看一下源码的流程

        点进 ServiceLoader.load(EatFood.class)中的load,可以到是为传进去的这个拓展点创建了一个服务加载器

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

        然后我们点进eatFoodServiceLoader.iterator()中的iterator,然后点进knownProviders.hasNext()

public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

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

        };
    }

        可以看到这样一段代码,然后点进hasNextService()

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 {
                    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;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        上图的方法就是判断是否有相对应的服务,可以看到中间的代码

        String fullName = PREFIX + service.getName();

        前者PREFIX就是就是我们刚刚创建的目录路径

java版本的dds java disp_dubbo

  service.getName()就是获取我们传进来的EatFood的全限定名

下面这段代码则是会解析该文件pending = parse(service, configs.nextElement());

然后我们再回到刚刚的迭代器,可以看到如果返回了True,继续获取对应的服务

java版本的dds java disp_后端_02

 然后在这边可以看到通过反射创建该服务的实例

java版本的dds java disp_java_03

         这就是java的Spi的流程,下面我们讲一讲Dubbo的Spi


二、Dubbo的SPI

        上面我们讲了Java的SPI,机智的小伙伴们肯定已经发现了缺点,就是没有办法选取特定的实例,只能通过迭代的方式进行操作。Dubbo中并没有使用Java的SPI,而是重新编写了一套逻辑,可以满足我们指定相对应的实例。

        先上一个例子

@SPI
public interface Robot {

	void sayHello();
}
public class Bumblebee implements Robot {

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

}

   

public class DubboSpiTest {

    //测试dubbo spi机制
    @Test
    public  void sayHello() throws Exception {
        //1、获得接口的ExtentionLoader
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        //2、根据指定的名字获(key)取对应的实例
        Robot robot = extensionLoader.getExtension("optimusPrime");
        robot.sayHello();
        /*Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();*/
//        Robot robot2 = extensionLoader.getDefaultExtension();
//        robot2.sayHello();
    }
}
bumblebee=com.itheima.spi.dubbo.impl.Bumblebee
optimusPrime=com.itheima.spi.dubbo.impl.OptimusPrime
wrapper=com.itheima.spi.dubbo.impl.RobotWrapper

        我们就从getExtensionLoader开始一步步解析

java版本的dds java disp_后端_04

         点进去,我们可以看到先是判断类型不能为空,其次是是否为接口,最后是是否拥有拓展注解,其实就是@SPI;接着就是从EXTENSION_LOADERS中根据传入的接口获取相对应的ExtensionLoader,这个EXTENSION_LOADERS其实是一个全局的ConcurrentHashMap,用于存放每个接口的相对应的ExtensionLoader,如果其中没有该接口的拓展加载器,则创建一个。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        /**
         * 校验
         * 1、不为空
         * 2、是接口
         * 3、接口上需要有@SPI注解
         */
        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() + "!");
        }
        /**
         * 先从EXTENSION_LOADERS(已加载的ExtensionLoader) 中获取
         * 每个接口type都对应一个 ExtensionLoader,该接口下会对应多个扩展点
         */
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 每个接口type都对应一个 ExtensionLoader
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

// 已加载的 Extension
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    // 缓存所有的实例Class及对应的实例对象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

java版本的dds java disp_开发语言_05

         到这里,怎么获取ExtensionLoader就讲完了,我们来看看怎么从加载器中获取实例的

java版本的dds java disp_后端_06

         先是进行了一下判空,然后判断了一下是否为true,如果为true的话则是会走@SPI中配置的默认实例;其次就是根据配置文件中的key,去cachedInstances中获取相对应的实例的Holder,可以看到cachedInstances其实就是一个ConcurrentHashMap作为一个缓存存放key相对应的实例的Holder的集合,Holder则是实例的封装。

        接下来就是从holder中获取实例,如果为空,则将holder锁住,然后再次判空,根据key创建实例,并且将它放入holder中(这里的代码采用的是单例模式中的DCL,也就是Double Check Lock)

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        /**
         * name为 META-INF/dubbo下配置文件中配置的key
         *
         * 1、获取key对应Extension实例的holder (双重锁校验)--->getOrCreateHolder内部通过cachedInstances缓存所有实例key及对应的Holder
         * 2、创建key对应的Extension实例并存入holder
         * 3、返回对应的Extension实例
         */
        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;
    }

    // 缓存 所有实例key及对应的Holder
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    private Holder<Object> getOrCreateHolder(String name) {
        /**
         * cachedInstances:缓存所有实例key及对应的Holder
         */
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

/**
 * Helper Class for hold a value.
 */
public class Holder<T> {

    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

         我们接着看创建实例的方法createExtension,首先是获取该接口下所有的拓展点的实例的Class,然后根据key从中获取我们需要的Class,我们可以看下获取的过程,从缓存cachedClasses中获取,如果该缓存为空,则进行拓展点Class的加载(执行loadExtensionClasses,此处又是一个DCL)

java版本的dds java disp_java_07

/**
     * 获取扩展Class
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {
        // Holder<Map<String, Class<?>>> cachedClasses
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

        接着我们来看加载的过程,可以看到dubbo设置了三种路径,一个是内部的加载,一个是对外使用的,最后一个是也提供了Java中SPI的路径支持,我们来看下(代码中的type为我们项目中的接口的全路径名)

private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();
        /**
         * loadDirectory方法从指定位置中加载拓展类配置
         * "META-INF/dubbo/internal/"  DubboInternalLoadingStrategy
         * “META-INF/dubbo/”,          DubboLoadingStrategy
         * "META-INF/services/",       ServicesLoadingStrategy
         */
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

        点进去我们看一下,fileName就是路径+接口的全路径名,正好构成我们配置文件的路径名,接下来获取了类加载器,然后进行了迭代,看下解析文件的代码

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        // dir 有三种目录  type是接口类型
        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);
        }
    }

        这里就是对配置文件内容的一些切割并取值,拿到key以及相对应的实例路径,然后根据实例的路径获取它的Class文件并加载,我们看下加载流程

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;
                            //按=切割 得到key和value
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim(); // 自定义的key
                                line = line.substring(i + 1).trim(); // value 即扩展点类的全路径
                            }
                            if (line.length() > 0) {
                                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);
        }
    }

        拿到Class文件会有三种情况:

  •         第一:判断它有没有Adaptive注解,如果有并且cachedAdaptiveClass为空,则放到缓存cachedAdaptiveClass中去
  •         第二:判断是否是Wrapper类型的Class,是通过查看该拓展点中是否有该接口类型的构造函数,如果有,也是将它放入到缓存cachedWrapperClasses中
  •         第三:则是普通的拓展实例
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.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 扩展点Class上有 Adaptive注解
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) { // 扩展点类有接口类型的构造函数,表明是Wrapper
            // 添加到Set<Class<?>> cachedWrapperClasses 缓存起来
            cacheWrapperClass(clazz);
        } else { // 证明是普通 extensionClasses
            clazz.getConstructor();
            if (StringUtils.isEmpty(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)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

private volatile Class<?> cachedAdaptiveClass = null;

private void cacheAdaptiveClass(Class<?> clazz) {
        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());
        }
    }

// 缓存WrapperClasses
    private Set<Class<?>> cachedWrapperClasses;

private void cacheWrapperClass(Class<?> clazz) {
        if (cachedWrapperClasses == null) {
            cachedWrapperClasses = new ConcurrentHashSet<>();
        }
        cachedWrapperClasses.add(clazz);
    }

private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

        这时候我们在回到这个地方,拿到了字节码文件,从缓存了所有实例的Class以及对应的实例对象的缓存中根据Class获取,如果没有,则给它创建实例并添加到缓存当中

java版本的dds java disp_dubbo_08

 

// 缓存所有的实例Class及对应的实例对象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

 dubbo的IOC       

        到这里,最普通的拓展点实例就讲完了,然后dubbo的SPI还远不止于此,它还可以像IOC一样,将一个拓展点放入另一个拓展点之中,不过需要在实例之中放入另一个拓展点的set方法,就像这样,下图为执行结果,那我们来看下是怎么做到的。

public class OptimusPrime implements Robot {


	private Protocol protocol;

	public void setProtocol(Protocol protocol) {
		this.protocol = protocol;
	}

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

java版本的dds java disp_java_09

在创建完拓展实例之后,dubbo又进行了一个操作,就是将它依赖的拓展点注入到这个实例当中去

java版本的dds java disp_java版本的dds_10

         可以看到先是获取实例的所有方法,然后先判断是否为set方法,根据开头是否已“set”开始,方法的入参是否只有一个并且是否是public修饰的;接着判断方法上有没有@DisableInject,没有的话,就继续往下;因为只有一个入参,所以直接拿0号位置的类型,接着判断是否入参类型是否为基本类型,因为本身不支持基本类型的注入;然后根据方法拿到属性名,根据类型以及属性名,我们就可以拿到待注入的拓展点,并把它注入到现在实例的方法之上

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    /**
                     * 通过set方法注入
                     */
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // set方法只能有一个参数
                        Class<?> pt = method.getParameterTypes()[0];
                        // set方法参数的类型如果是基本数据类型则跳过,即不支持基本数据类型的注入
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 获取set方法对应的属性名称
                            String property = getSetterProperty(method);
                            /**
                             * pt:属性类型Class
                             * property:属性名称
                             *
                             * 根据类型和名称获取待注入的Extension实例
                             * ExtensionFactory objectFactory;
                             *   实现有很多比如:
                             *      SpiExtensionFactory
                             *      SpringExtensionFactory
                             */
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                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;
    }
/**
     * return true if and only if:
     * <p>
     * 1, public
     * <p>
     * 2, name starts with "set"
     * <p>
     * 3, only has one parameter
     */
    private boolean isSetter(Method method) {
        return method.getName().startsWith("set")
                && method.getParameterTypes().length == 1
                && Modifier.isPublic(method.getModifiers());
    }

  dubbo的AOP

        熟悉Spring的都知道,AOP可以对方法进行切面增强,dubbo的SPI也是可以的,先上案例

bumblebee=com.itheima.spi.dubbo.impl.Bumblebee
optimusPrime=com.itheima.spi.dubbo.impl.OptimusPrime
wrapper=com.itheima.spi.dubbo.impl.RobotWrapper
public class RobotWrapper implements Robot {


    private Robot robot;

    public RobotWrapper(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void sayHello() {
        System.out.println("----提前准备----");
        robot.sayHello();
        System.out.println("----收尾工作----");
    }
}

执行结果

java版本的dds java disp_后端_11

        可以看到实现了跟AOP一样的效果,我们来看下实现原理,之前在拿到Class文件之后呢,就有对这个Class判断是否为Wrapper的逻辑,记不得的同志可以看下上面的讲解。在这里注入完依赖的实例后,我们从缓存中拿到wrapper类型的Class的set集合,然后遍历WrapperClass的集合。

        然后通过WrapperClass的构造函数将上面的实例进行注入,另外它本身可能还会有依赖其他拓展点,所以还需要injectExtension()一下。,如果有多个wrapper,则每次注入的都是之前的整体。

java版本的dds java disp_后端_12

 动态编译

        dubbo的SPI还有最后一个知识点,就是拓展点有多个实例,它又是如何自己去动态加载我们需要的实例的。我们回到这个地方,点进去

java版本的dds java disp_java版本的dds_13

         可以看到本身就是一个拓展点,拥有多个实例,可以看到甚至可以从IOC中拿到实例当做拓展点实例。

@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     *
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}

java版本的dds java disp_后端_14

         我们点进SpiExtensionFactory,可以看到先是对Class进行基本的判断,然后获取该拓展点加载器的自适应拓展实例。

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

又是DCL形式的,从缓存中拿,没有就创建,创建之后放入缓存        

public T getAdaptiveExtension() {
        // Holder<Object> cachedAdaptiveInstance
        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("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

我们看下创建自适应实例的过程,其实这里并不是手写代码执行的,而是通过框架javassist生成代码存到下面的String code中

/**
     * 创建接口的自适应实例
     */
    private T createAdaptiveExtension() {
        try {
            //  getAdaptiveExtensionClass()是核心
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 获取接口自适应实例Class
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    private Class<?> createAdaptiveExtensionClass() {
        /**
         * 首先会生成自适应类的Java源码,然后再将源码编译成Java的字节码,加载到JVM中
         * 使用一个StringBuilder来构建自适应类的Java源码;
         * 这种生成字节码的方式也挺有意思的,先生成Java源代码,然后编译,加载到jvm中。
         * 通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。
         * 因为xxx.java文件是Java通用的,也是我们最熟悉的。只是代码的可读性不强,需要一点一点构建xx.java的内容
         */
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        /**
         * @SPI("javassist")
         * public interface Compiler
         */
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

将其中的代码截出来,可以看到如果没有配置,则会去选取“dubbo”实例,有的话则用我们配置的,最后是通过它的接口获取拓展点加载器,再勇气获取拓展点实例,最后用这个实例执行

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

以上,就是dubbo的SPI的全部内容,有错误麻烦指出。