欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Dubbo SPI详解

程序员文章站 2022-06-01 19:46:45
...

Dubbo SPI的使用

ExtensionLoader<Person> extensionLoader = ExtensionLoader.getExtensionLoader(Person.class);

        Person person = extensionLoader.getExtension("black");

通过ExtensionLoader得到Person类的扩展点,然后通过getExtension方法,可以得到这个接口扩展点下面的子类,并可以使用这个类的方法,看过 Java SPI的应该都了解,那么Dubbo里的代码是怎么写的呢?

 

ExtensionLoader

看看getExtensionLoader方法的代码

 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!");
        }
        //一定要有SPI的注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        //一个type对应一个loader类,先从缓存中查看没有的话new个新的
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //每一个接口type对应的扩展点加载器
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

可以看到参数的属性必须要是接口,而且要有SPI的注解。首先会在一个EXTENSION_LOADERS的ConcurrentMap缓存里看里面有没有这个接口的扩展点加载器,

没有的话就会调用这个map里的putIfAbsent,生成一个ExtensionLoader,并放进map里

查看ExtensionLoader的构造方法

private ExtensionLoader(Class<?> type) {
        //表示扩展点加载器所要加载的接口的类型
        this.type = type;
        // objectFactory表示当前ExtensionLoader内部的一个对象工厂,可以用来获取对象  得到ExtensionFactory接口的adaptive实例  AdaptiveExtensionFactory实例,利用AdaptiveExtensionFactory实例来获取某个类
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

这个objectFactory,我认为就是一个工厂类,是用来获取对象的。这个其实也是用ExtensionLoader来获得ExtensionFactory的扩展点再调用getadAdaptiveExtension()方法来得到的。

这个是用来得到带有Adaptive注解的接口实现类,可以看到AdaptiveExtensionFactory类,上游Adaptive注解,而它的getExtension方法,也就是获取对象的方法的代码是

public <T> T getExtension(Class<T> type, String name) {
        // 遍历两个ExtensionFactory,从ExtensionFactory中得到实例,只要从某个ExtensionFactory中获取到对象实例就可以了
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);  // SpringExtensionFactory,, SpiExtensionFactory
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

可以看到是分别从factories集合里得到的ExtensionFactory进行getExtension方法,获得对象。其实是调用SpiExtensionFactory和SpringExtensionFactory的getExtension方法

SpringExtensionFactory的getExtension方法

public <T> T getExtension(Class<T> type, String name) {

        //SPI should be get from SpiExtensionFactory
        // 如果接口上存在SPI注解,就不从spring中获取对象实例了
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }

        // 从ApplicationContext中获取bean, byname
        for (ApplicationContext context : CONTEXTS) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

        if (Object.class == type) {
            return null;
        }

        // byType
        for (ApplicationContext context : CONTEXTS) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");

        return null;
    }

可以看到是通过spring get bean的方式来获取根据name或type来获取。也可以得知,如果和spring整合,Dubbo中的一些对象都会弄成bean放进spring里。

看SpiExtensionFactory的getExtension方法

 public <T> T getExtension(Class<T> type, String name) {


        // 接口上存在SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);


            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension(); // 接口的Adaptive类(代理对象)
            }
        }
        return null;
    }

这些后面再看

 

 

接着初始化完ExtensionLoader后

会通过这个扩展点加载器的getExtension方法来得到类对象

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        // 获取默认扩展类
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();

        // 如果有两个线程同时来获取同一个name的扩展点对象,那只会有一个线程会进行创建
        if (instance == null) {
            synchronized (holder) { // 一个name对应一把锁
                instance = holder.get();
                if (instance == null) {
                    // 创建扩展点实例对象
                    instance = createExtension(name);   // 创建扩展点对象
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

再看createExtension方法

private T createExtension(String name) {
        // 获取扩展类  {name: Class}  key-Value    接口的所有实现类
        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);
            }

            // 依赖注入 IOC
            injectExtension(instance);

            // AOP,cachedWrapperClasses无序
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //生成一个Wrapper实例(传入了instance),然后进行依赖注入
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));// new xxwrapper(实例)
                }
            }

            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

第一步获取扩展类的方法getExtensionClasses()

private Map<String, Class<?>> getExtensionClasses() {
        // cachedClasses是一个Holder对象,持有的就是一个Map<String, Class<?>>
        // 为什么要多此一举,也是为了解决并发,Holder对象用来作为锁

        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses(); // 加载、解析文件 Map
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

逻辑在loadExtensionClasses()里

private Map<String, Class<?>> loadExtensionClasses() {
        // cache接口默认的扩展类
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        //加载解析配置文件中的对应类的所有类
        //都会对这个map里面put值
       // 类似org.apache.dubbo.rpc.Protocol           "META-INF/dubbo/internal/"
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        //把加载解析的配置文件从"org.apache."替换成了"com.alibaba."
        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;
    }

重点是下面的loadDirectory。别看它有这么多loadDirectory怪吓人的,但仔细看就可以看到其实只是后面的一个参数不同而已,都是同一个方法,就是加载文件的路径不同

    //配置文件位置1,沿用java的目录
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    //配置文件位置
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    //配置文件位置2,dubbo自定义的目录
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

可以对应着看看文件是在哪配置的。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            // 根据文件中的内容得到urls, 每个url表示一个扩展    http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
            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();
                    // 遍历url进行加载,把扩展类添加到extensionClasses中
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
           
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

上面通过加载文件,可以得到一个urls集合,我随便找一个,就是这种,一行对应着一个url

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

而loadResource就是加载url

 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) {
                                //key
                                name = line.substring(0, i).trim();
                                //value
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // 加载类,并添加到extensionClasses中
                                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);
        }
    }

可以看到,在这个方法里面,会判断这一行有没有#号,有的话就是注释就不读了,然后它判断有没有等号,也就是左边和右边,分别是name和我们的类,然后分别传进这个loadClass方法里,并且还用Class.forName反射得到了这个类。

而我们最后要得到的就是这个extensionClasses这个map,所以这时候我想的是这方法里面是把这个类和name,put到这个map里去,看看代码是不是呢

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)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            // 是一个Wrapper类
            //
            //是Wrapper类,就缓存进一个set里
            cacheWrapperClass(clazz);
        } else {
            // 需要有无参的构造方法
            clazz.getConstructor();

            // 在文件中没有name,但是在类上指定了Extension的注解上指定了name
            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)) {
                // 缓存一下被Activate注解了的类
                cacheActivateClass(clazz, names[0]);

                // 有多个名字
                for (String n : names) {
                    // clazz: name
                    cacheName(clazz, n);
                    // name: clazz
                    //把类以及name存进map
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

其实没有这么简单,这代码挺长的。它会判断这个类有没有带Adaptive注解,是不是wrapper类。因为我们传进来的这个name,它不是一个接口,它是我们需要通过接口的扩展点获取的类。判断是不是wrapper就看它能不能拿到type参数对应的构造方法。如果是Adaptive类或者是个wrapper类,就会放进缓存里。如果不是这些就都会放进这个map里。

然后回到createExtension方法,得到这个map后,通过get(name),我们可以得到这个扩展类。而Dubbo再得到扩展类后,还进行了很多操作

injectExtension(instance);
private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }

                // 利用set方法注入

                /**
                 * 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];   // Person接口
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 得到setXxx中的xxx
                    String property = getSetterProperty(method);   // person

                    // 根据参数类型或属性名,从objectFactory中获取到对象,然后调用set方法进行注入
                    // AdaptiveExtensionFactory
                    Object object = objectFactory.getExtension(pt, property); // User.class, user
                    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;
    }

这里可以看到,通过遍历instance类的各个方法,判断这个方法是不是setxxx,来进行依赖注入,而怎么依赖注入的呢,就是用到了之前提到的objectFactory,之前提到,是可以通过SPI,或者spring,他们的getExtension方法,其实SPI就是和咋们这个方式差不多,通过配置文件去找,Spring就是在IOC容器里,找bean。

然后会遍历这个cacheWrapperClasses,也就是遍历这个接口的所有wrapper类。把实例对象传进wrapper里去,通过依赖注入。

public class Wrapper<T> {
    private T data;

    Wrapper(T data) {
        this.data = data;
    }

    Object getData() {
        return data;
    }
}

随便在Dubbo找一个Wrapper类的定义,可以看到它里面是一个带有构造方法的,所以这里是通过依赖注入,将对象注入进这个wrapper对象里。