Dubbo SPI详解

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对象里。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值