首页 > 图灵资讯 > 技术篇>正文

Dubbo2.7的Dubbo SPI实现原理细节

2023-11-13 15:41:22

总结/朱季谦

这篇文章主要记录我对Dubbo 对SPI实现原理的理解,至于什么是SPI,我不会像其他博客那样详细地从概念到Java SPI仔细分析,直接开门见山分享我的Dubo 对SPI的看法。

Dubbo SPI的机制与Spring相似 IOC的getBean()加载,当输入存在的beannname时,可以返回beannnname对应的对象。同样,在Dubo 在SPI中,我们还输入了一个存在的name,dubbo框架将自动返回key对应的对象。不难猜测,Dubbo SPI与Spring IOC在底部应该有一个大致相似的逻辑。简单来说,两者都可以通过beanname或key值在框架中的map缓存中找到相应的class类名,然后反射class类名生成对象,初始化完成对象,最后返回一个完整的对象。然而,在这个过程中,Spirng将相对更加复杂,其中有一堆后处理器...

举个简单的例子,大致解释一下Dubo SPI实现原理,然后进一步分析源代码。

首先,我在org.apache.dubbo.在test目录下,定义一个@SPI注释到接口:

package org.apache.dubbo.test;import org.apache.dubbo.common.extension.SPI;@SPI("dog")public interface Animal {    void haveBehavior();}

然后,在同一目录下,为Dog创建两个实现接口的类别,Cat。

Dog——

package org.apache.dubbo.test;public class Dog implements Animal {    @Override    public void haveBehavior() {        System.out.println("狗会叫");    }}

Cat——

package org.apache.dubbo.test;public class Cat implements Animal {    @Override    public void haveBehavior() {        System.out.println("猫会抓老鼠");    }}

请注意,Animal接口的类别是org.apache.dubbo.test.Animal,接下来,我们将在resource目录/META_INF/dubo需要新建一个与接口名相对应的File文件,文件名与Animal接口的类名相同:org.apache.dubbo.test.Animal。两个名字之所以要一致,是因为只需要拿到Animal接口的类名,到resource目录/META_INF/dubbo,通过这个类名,可以定位到与Animal接口对应的文件。

文件名org在Dubbo中.apache.dubbo.test.事实上,Animal的文件中有类似Spring的文件 那种bean<bean id="cat" class="org.apache.dubbo.test.Cat"/>的数据,也就是说,id对应bean class的形式——

cat=org.apache.dubbo.test.Catdog=org.apache.dubbo.test.Dog

这两行数据是Animal接口实现Cat和DogClass的全限名。

整个目录结构是这样的——image

最后,写一个测试类Dubospitestest演示效果——

package org.apache.dubbo.test;import org.apache.dubbo.common.extension.ExtensionLoader;import org.junit.jupiter.api.Test;class DubboSPITestTest {    @Test    public void test(){        Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");        dog.haveBehavior();        Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");        cat.haveBehavior();    }}

执行结果如下——

image

让我们简单地看看这个想法是如何实现的,ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")这行代码内部,根据Animal接口完整org.apache.dubbo.test.Animal在指定目录下找到同名File文件org.apache.dubbo.test.Animal,然后循环分析文件中的内容,以key-value的形式加载到map缓存中。

这样的操作(当然源码会更复杂)——

Map<String,String> map = new HashMap<>();map.put("cat","org.apache.dubbo.test.Cat");map.put("dog","org.apache.dubbo.test.Dog");

当然,在真实源代码中,value存储的是基于类名的class,但事实上,无论是类名还是class,最终都可以反射生成对象,

此时,您可以根据操作代码动态获取接口对应的实现类。例如,org需要使用.apache.dubbo.test.Cat实现类,然后调用getextension("cat")在方法中,我们输入的参数是"cat",从刚刚分析文件缓存的map中,根据map.get("cat")拿到相应的org.apache.dubbo.test.Cat。既然你能得到类名,你就不能通过反射生成这样的对象吗?既然你可以得到类名,你就不能通过反射生成这样的对象吗?当然,在生成的对象中可能还有一个需要注入的属性,即Dubbo IOC的功能将在源码分析中进一步解释。当对象完成初始化时,将返回生成的对象指向其界面引用Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")。

整个过程是根据代码动态获取接口的实现类,方便灵活调用,同时实现接口和实现类的解耦。

Dubbo SPI在Java 在SPI的基础上进行扩展,Java 在SPI中与接口同名的文件中,不是key- value的形式纯粹是按行直接存储各实现类的类名,比如这样——

org.apache.dubbo.test.Catorg.apache.dubbo.test.Dog

这意味着,Java 在实现过程中,当SPI通过接口名定位读取resource中的接口同名文件时,无法根据key值选择接口实现类。它只能读取所有内容,然后循环获取相应的接口实现类调用方法。这意味着可能会有一些不需要调用的实现类,也会被加载并生成对象一起返回,无法按需获得。

因此,Dubo在原有的基础上补充了Java 如上述所述,SPI无法通过某个key值调用指定的接口实现类,Dubbo SPI可以通过catkey按需返回相应的org.apache.dubbo.test.实现Cat类的对象。

下面就来分析一下具体实现的原理细节,下面的代码做案例。

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");cat.haveBehavior();

首先,分析Extensionloder.getExtensionLoader(Animal.class)方法——

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() + "!");    }    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;}

在这种方法中,如果传输的Class参数是空的或非接口的,或者没有@SPI注释,则会抛出一个Illlegalargumentexception异常,表明传输的Class必须满足非空的要求,才能正常执行。我们在这里传输的是Animal.class,满足上述三个条件。

@SPI("cat")public interface Animal {    void haveBehavior();}

接下来,在最后一部分代码中,主要是创建Extensionloader对象。

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;

最后,创建的Extensionloader对象返回,包括两件事,一件是type,另一件是objectfactory。这两件事将用于后源代码。image

创建Extensionloader对象后,将开始调用getextension方法——

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");

进入getextension("cat")在方法中,另一种重载方法将在内部调用(name, true)。

public T getExtension(String name) {    return getExtension(name, true);}

让我们来看看该方法的内部实现——

public T getExtension(String name, boolean wrap) {    if (StringUtils.isEmpty(name)) {        throw new IllegalArgumentException("Extension name == null");    }    if ("true".equals(name)) {        return getDefaultExtension();    }    //step 1    final Holder<Object> holder = getOrCreateHolder(name);    Object instance = holder.get();        //step 2    //双重检查    if (instance == null) {        synchronized (holder) {            instance = holder.get();            if (instance == null) {                //创建扩展实例                instance = createExtension(name, wrap);                ///设置实例到holeder                holder.set(instance);            }        }    }    return (T) instance;}

该方法主要有两部分。

step 1.首先从缓存中找到name,“cat是否存在对象,即调用getorcreateholder(name),在这种方法中,会在cachedInstances缓存中搜索。cachedinstances定义为concurentmapp<String, Holder<Object>>map缓存。若cachedinstancess.get(name)如果回到null,说明缓存中没有name对应的对象数据,那么key值为name,value值为newe Holder<>()缓存键值。

private Holder<Object> getOrCreateHolder(String name) {    Holder<Object> holder = cachedInstances.get(name);    if (holder == null) {        cachedInstances.putIfAbsent(name, new Holder<>());        holder = cachedInstances.get(name);    }    return holder;}

想必你会有疑惑,为什么要在这里创造一个new? Holder<>()对象呢?

当你进入Holder类时,你会发现一个泛变量value用private装饰包装,这意味着外部类不能修改value值,可以起到包装保护的作用。我们正在通过name=cat去得到org.apache.dubbo.test.如果Cat实现对象能够正常生成,最终将其包装到Holder对象中,然后将Holder对象存储在CachedInstances缓存中。

public class Holder<T> {    private volatile T value;    public void set(T value) {        this.value = value;    }    public T get() {        return value;    }}

因此,Holder的操作应该从缓存中获得——

//根据name,“cat“去缓存中查找包装org.apache.dubbo.test.Cat对象的Holder对象。final Holder<Object> holder = getOrCreateHolder(name);///如果能找到,从Holder对象取出内部封装对象Object instance = holder.get();

如果holder.get()获得的对象为null,表示该对象尚未生成。cat相应的org.apache.dubbo.test.Cat类对象。

然后,它将继续向下执行——

//双重检查if (instance == null) {    synchronized (holder) {        instance = holder.get();        if (instance == null) {            //创建扩展实例            instance = createExtension(name, wrap);            ///设置实例到holeder            holder.set(instance);        }    }}

这里使用了双重检查操作,以避免一个线程创建一半,另一个线程开始创建相同的对象,这将是一个问题。

这一行instancee = createExtension(name, wrap)代码的主要功能是获得“cat相应的org.apache.dubbo.test.Cat类对象,然后通过holder返回返回的对象.set(instance)封装在Holder对象中。

private T createExtension(String name, boolean wrap) {    // step 从配置文件中加载所有扩展类,可以得到"配置项名称"到"配置类"映射关系表    Class<?> clazz = getExtensionClasses().get(name);    if (clazz == null) {        throw findException(name);    }    try {        T instance = (T) EXTENSION_INSTANCES.get(clazz);        if (instance == null) {            //step 2  clazz将通过反射创建实例对象。            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());            instance = (T) EXTENSION_INSTANCES.get(clazz);        }        //step 3  依赖注入实例对象的属性,即Dubbo IOC逻辑。        injectExtension(instance);        if (wrap) {            List<Class<?>> wrapperClassesList = new ArrayList<>();            if (cachedWrapperClasses != null) {                wrapperClassesList.addAll(cachedWrapperClasses);                wrapperClassesList.sort(WrapperComparator.COMPARATOR);                Collections.reverse(wrapperClassesList);            }            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {                for (Class<?> wrapperClass : wrapperClassesList) {                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);                    if (wrapper == null                            || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {                        ///将当前instance作为参数传递给Wrapper的构造方法,通过反射创建Wrapper实例                        //然后将依赖注入Wraper实例,最后,Wraper实例再次赋值instance变量                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));                    }                }            }        }        initExtension(instance);        //step 4 返回初始化完成的对象        return instance;    } catch (Throwable t) {        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +                type + ") couldn't be instantiated: " + t.getMessage(), t);    }}

createExtension(String name, boolean wrap)以下方法主要实现

step 1 可以从配置文件中加载所有扩展类别"配置项名称"到"配置类"映射关系表。

step 2 clazz将通过反射创建实例对象。

step 3 依赖注入实例对象的属性,即Dubbo IOC逻辑。

step 4 返回初始化完成的对象

首先,让我们来看看代码分析的第一步——

// step 从配置文件中加载所有扩展类,可以得到"配置项名称"到"配置类"Classs的映射关系表<?> clazz = getExtensionClasses().get(name);

其中,getextensionClases()的方法是获取Mapp,返回一个分析界面对应Resource中文件的Mapp<String, Class<?>>缓存,代码的最后部分得到(name)在这种情况下,它是基于“cat”获得“org.apache.dubbo.test.Cat"Class。cachedclassess内部方法.get()返回的Mapp<String,Class<?>> classes存储在与Resource文件相对应的Key- value数据,即carrue=org.apache.dubbo.test.Cat和dog=org.apache.dubbo.test.像Dog这样的。

private Map<String, Class<?>> getExtensionClasses() {    //从缓存中获取已加载的扩展类:car=org.apache.dubbo.test.Cat    Map<String, Class<?>> classes = cachedClasses.get();    //双重检查    if (classes == null) {        synchronized (cachedClasses) {            classes = cachedClasses.get();            if (classes == null) {                classes = loadExtensionClasses();                cachedClasses.set(classes);            }        }    }    return classes;}

当然,首次调用cachedclasseses.get()返回值classes必须是null。在classes==null中,也使用了双重检查操作,最后将调用loadextensionclases()方法,该方法的主要工作之一是读取resource中接口对应的文件进行分析,然后用key-value将分析数据缓存到map中<String, Class<?>>最后,通过cachedclasseses,.set(classes)存储在cachedclases中,这里的cachedclases也是final定义的holder对象,其功能与上述一致,包装在内部用private装饰,防止外部损坏。

主要看loadextensionClases()内部逻辑——

private Map<String, Class<?>> loadExtensionClasses() {    //step 1 分析SPI注解,获得SPI默认value    cacheDefaultExtensionName();    Map<String, Class<?>> extensionClasses = new HashMap<>();    /**     * step 2 strategies包含以下四种策略,代表在四个不同的目录下搜索文件:     *     *      DubboInternalLoadingStrategy 表示目录"META-INF/dubbo/internal/"     *      DubboExternalLoadingStrategy 表示目录""META-INF/dubbo/external/""     *      DubboLoadingStrategy 表示目录"META-INF/dubbo/"     *      ServicesLoadingStrategy 表示目录"META-INF/services/"     */    for (LoadingStrategy strategy : strategies) {        //加载指定文件夹下配置文件,找到SPI默认对value的classs        //apache        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());        //alibaba        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());    }    return extensionClasses;}

首先,执行cachedefaultextensionnname()方法,分析接口修改的@SPI,获取注释中的value值。例如,在这个例子中,Animal的注释@SPI("cat"),然后,通过cachedefaultextensionnnname(),可以获得注释@SPI中的默认值。cat”。注释值之所以被用作默认值,是因为如果没有引入指定的name,则返回cat对应的类对象。

image

然后,通过四个不同的目录,找出是否有与接口Animal相对应的文件。这里的strategies是一个包含四个对象的数组,每个对象代表查找一个目录,包括"META-INF/dubbo/internal/"、"META-INF/dubbo/external/"、"META-INF/dubbo/"、"META-INF/services/",表示分别到这四个目录查看是否有满意的文件。

在for循环中,loaddirectory方法被调用了两次。一个是搜索apache版本,另一个是搜索alibaba版本。这两种方法的底部实际上是相同的。只需注意其中一个即可实现。

   /**     * step 2 strategies包含以下四种策略,代表在四个不同的目录下搜索文件:     *     *      DubboInternalLoadingStrategy 表示目录"META-INF/dubbo/internal/"     *      DubboExternalLoadingStrategy 表示目录"META-INF/dubbo/external/"     *      DubboLoadingStrategy 表示目录"META-INF/dubbo/"     *      ServicesLoadingStrategy 表示目录"META-INF/services/"     */    for (LoadingStrategy strategy : strategies) {        //加载指定文件夹下配置文件,找到SPI默认对value的classs        //apache        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());        //alibaba        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());    }

在loaddirectory方法中,将File文件定位到接口对应的位置,获取文件的路径,然后调用loadresource方法分析文件——

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {    ///文件夹路径+type 全限定名:META-INF/dubbo/internal/org.apache.dubbo.test.Animal    String fileName = dir + type;    try {        Enumeration<java.net.URL> urls = null;        ClassLoader classLoader = findClassLoader();        // try to load from ExtensionLoader's ClassLoader first        if (extensionLoaderClassLoaderFirst) {            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {                urls = extensionLoaderClassLoader.getResources(fileName);            }        }        if (urls == null || !urls.hasMoreElements()) {            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, overridden, excludedPackages);            }        }    } catch (Throwable t) {        logger.error("Exception occurred when loading extension class (interface: " +                type + ", description file: " + fileName + ").", t);    }}

loadresource的方法主要是阅读File文件资源,然后将文件中的每一行记录循环,跳过开头的注释记录=org.apache.dubbo.test.切割Cat形式的行记录。通过此行代码int i = line.indexOf('=')定位到等于号=的位置,然后用namee = line.substring(0, i).trim()截取等于号码前面的字符串作为key, 以 line = line.substring(i + 1).trim()截取等于号=后面的字符串作为value,形成key-value键对形式数据,进一步传输到 相应缓存loadClass方法。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,                          java.net.URL resourceURL, boolean overridden, String... excludedPackages) {    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;                        int i = line.indexOf('=');                        if (i > 0) {                            //等于号 = 为界,读key健 value值                            name = line.substring(0, i).trim();                            line = line.substring(i + 1).trim();                        }                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {                            //加载类通过loadClass对类缓存                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);                        }                    } 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);    }}

以cat=org.apache.dubbo.test.以Cat数据为例,debug可以看到,最终分析为——

image

最后,去loadClass看看如何缓存从文件中分析的key-value数据。需要注意的是,在执行此方法时,已经获得了上述列表="org.apache.dubbo.test.Cat"通过Class.forName(line, true, classLoader)生成相应的Class。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,                       boolean overridden) 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, overridden);    } else if (isWrapperClass(clazz)) {        cacheWrapperClass(clazz);    } else {        //程序进入这个分支,表明class是一个普通的扩展类        ///检测class是否有默认的结构方法,如果没有,则抛出异常        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);            }        }        //names = ["cat"]        String[] names = NAME_SEPARATOR.split(name);        if (ArrayUtils.isNotEmpty(names)) {            cacheActivateClass(clazz, names[0]);            for (String n : names) {                ///存储Class到名称的映射关系                cacheName(clazz, n);                ///从存储名称到Class的映射关系                saveInExtensionClass(extensionClasses, clazz, n, overridden);            }        }    }}

这里只需要关注最后的saveinextensionClass方法,最后将从文件中分析出来cat”-->org.apache.dubbo.test.将Cat存入Map<String, Class<?>> 在extensionClases缓存中。

image

这个Map<String, Class<?>> extensionclasses缓存是在loadextensionclases()方法中创建的。loadextensionclases方法最终将返回extensionclaseses。

private Map<String, Class<?>> loadExtensionClasses() {    ...    //创建key-value数据,用于缓存存储解析文件    Map<String, Class<?>> extensionClasses = new HashMap<>();    for (LoadingStrategy strategy : strategies) {        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());        ...    return extensionClasses;}

到这一步,Animal接口对应的resource/META-INF/dubbo/org.apache.dubbo.test.Animal文件分析,分析的数据存储在extensionClases这个Map缓存中。

image

回顾之前的调用方法,可以看出,最终得到extensionclases的map缓存将返回getextensionclases()方法,因此,在creatension中调用getextensionclaseses().get(name),相当于调用extensionClasesses.get(name)。因为传输到方法中的参数name="cat",因此,返回的Class是org.apache.dubbo.test.Cat。

image

然后向下执行,到EXTENSION_代码INSTANCES.putIfAbsent(clazz, clazz.newInstance()是通过clazzz.newInstance()反射创建了一个暂时或空属性的对象,同时缓存到EXTENSION_INSTANCES缓存中,这是ConcurentMap<Class<?>, Object>缓存,避免反复反射创建对象。image

实例化完成org.apache.dubbo.test.创建Cat对象的下一步是通过injectextension(instance)依赖注入对象。主要功能类似于Spring 当Bean在IOC中有@Resource或@Autowired注释的属性时,Bean需要在实例化创建对象后补充属性,即将到来@Resource或者@Autowired注释的属性通过反射指向另一个bean对象。在Dubo 在IOC中,也是类似的实现。首先,对非setxxx()方法进行过滤,只对setxx()方法进行处理。这种处理方法是截取set背后的字符。例如,有这样一个sethitinformservicee (HitInformService hitInformService)方法,然后将set后面的字符截取,并对截取后的第一个字符进行小写处理,得到“hitInformService”。需要注意的是,如果类型是数组,则在参数中获得Hitinformservice的类型,String、Boolean、Character、Number、其中一个Date不会注入。相反,它将继续向下执行。

/** * Dubbo 目前IOC只支持setter注入 * @param instance * @return */private T injectExtension(T instance) {    if (objectFactory == null) {        return instance;    }    try {        ///遍历目标类的所有方法        for (Method method : instance.getClass().getMethods()) {            //检测方法是否以set开始,该方法只有一个参数,该方法的访问级别为public            if (!isSetter(method)) {                continue;            }            /**             * Check {@link DisableInject} to see if we need auto injection for this property             */            if (method.getAnnotation(DisableInject.class) != null) {                continue;            }            ////获得setter方法参数类型            Class<?> pt = method.getParameterTypes()[0];            ///判断对象是否为数组,String、Boolean、Character、Number、如果是Date类型,跳出这个循环,继续下一个循环            if (ReflectUtils.isPrimitives(pt)) {                continue;            }            try {                ///获取属性名,比如 setname方法对应属性名name                String property = getSetterProperty(method);                /**                 * objectFactory 变量类型为 AdaptiveExtensionFactory,                 *      AdaptiveExtensionFactory 一个内部维护 ExtensionFactory 列表,用于存储其它类型的列表 ExtensionFactory。                 *                 * Dubbo 目前有两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。                 *      前者用于创建自适应扩展,后者用于从 Spring 的 IOC 在容器中获得所需的扩展。                 *                 */                ///从ObjectFactory获取依赖对象                Object object = objectFactory.getExtension(pt, property);                if (object != null) {                    ///通过反射调用setter方法依赖                    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;}

执行 Object object = objectFactory.getExtension(pt, property)到行代码是获取Hitinformservice hitinformservice引用相应的对象,这里有两种获取方法,一是通过SpringensionFactory通过getbeann(name)通过Spring加载bean获取对象,另一种是通过本文的Dubo SPI方法,根据name对文件中相应的接口进行分析,实现类Class反射生成返回。

无论以何种方式,最终都需要获得返回对象,然后通过method返回对象.invoke(instance, object)反射执行相应的setxx()方法,将对象的属性注入上述SPI创建的对象cat中。

在这里,接口Animal对应cat的实现类创建已经完成。这个过程就是Dubbo 实现SPI底层的细节。最后,将获得org.apache.dubbo.test.Cat对象向上指向其接口Animal引用,可通过接口调用实现类重写的havebehavior方法。

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");cat.haveBehavior();

上一篇 des cbc java 算法
下一篇 Java SPI机制总结系列之开发入门实例

文章素材均来源于网络,如有侵权,请联系管理员删除。