您当前的位置: 首页 >  dubbo

恐龙弟旺仔

暂无认证

  • 0浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Dubbo源码解析-SPI的应用

恐龙弟旺仔 发布时间:2021-11-19 12:34:07 ,浏览量:0

前言:

    Dubbo中大量使用了SPI的技术,实现各调用层之间的解耦。所以,了解SPI技术的实现还是很有必要的。

    JDK本身也有SPI的实现,Dubbo对该技术实现了增强。

1.SPI

    SPI(Service Provider Interface),是java提供的一套用来被第三方实现或扩展的接口。而SPI技术就是用来查找这些扩展实现。

    概念相对比较难懂,读者可以参考这篇博文(笔者是没太读懂):https://www.cnblogs.com/happyframework/archive/2013/09/17/3325560.html 

    我们直接上示例,示例相对更好明白些。

2.JDK的SPI 2.1 提供接口和一个实现类
// Teacher
package xw.demo.adaptive;
public interface Teacher {
    void say();
}

// 实现类,提供三个实现类
package xw.demo.adaptive;
public class ChineseTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("chinese");
    }
}

public class EnglishTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("english");
    }
}

public class MathTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("math");
    }
}
2.2 创建文件

    在当前项目resources目录下 新建META-INF/services目录,并在这个目录创建一个与接口全限定名一致的文件(即xw.demo.adaptive.Teacher),文件内容为实现类的全限定名。如下所示

 

2.3 ServiceLoader加载
ServiceLoader teachers = ServiceLoader.load(Teacher.class);
for(Teacher t : teachers) {
    t.say();
}
// 结果为:
chinese
math
english

看结果值,我们知道ServiceLoader加载了上面Teacher的三个实现类,并成功调用了方法。

2.4 ServiceLoader源码解析
public final class ServiceLoader implements Iterable{
    private static final String PREFIX = "META-INF/services/";
    
    public static  ServiceLoader load(Class service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    public static  ServiceLoader load(Class service, ClassLoader loader)
    {
        return new ServiceLoader(service, loader);
    }
    
    private ServiceLoader(Class svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        // 重点在这里了
        reload();
    }
    
    public void reload() {
        providers.clear();
        // 创建了一个LazyIterator
        lookupIterator = new LazyIterator(service, loader);
    }
    
    // 我们再来看下ServiceLoader.iterator方法,
    public Iterator iterator() {
        return new Iterator() {

            Iterator 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();
                // 都交由LazyIterator来实现了
                return lookupIterator.next();
            }

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

        };
    }
}

经过上面的一系列调用分析,我们知道,ServiceLoader.iterator 的next()调用都交由LazyIterator来实现了

private class LazyIterator implements Iterator{
        Class service;
        ClassLoader loader;
        Enumeration configs = null;
        Iterator pending = null;
        String nextName = null;

        private LazyIterator(Class service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
        ...
            
		public S next() {
            if (acc == null) {
            // 直接交由nextService实现
                return nextService();
            } else {
                PrivilegedAction action = new PrivilegedAction() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
    
        private S nextService() {
            // 调用hasNextService()实现
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
               ...
            }
            ...
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                ...
            }
            throw new Error();          // This cannot happen
        }
       
    	private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 本例中即META-INF/services/xw.demo.DemoService
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 加载该路径下的文件,到Enumeration configs中
                        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即文件内容,本例中即xw.demo.DemoServiceImpl
            nextName = pending.next();
            return true;
        }
    }

代码不算复杂,主要就是解析META-INF/services目录下的以接口全限定名为文件名的文件,解析文件的内容,并创建对应Class的实例

2.5 ServiceLoader的应用

    JDK提供的ServiceLoader技术,有很多广泛的应用。最为大家所熟悉的应该就是DriverManger了。

    DriverManger在加载的时候,就依据ServiceLoader的方式扫描了一下当前有哪些Driver的实现,并主动加载。

public class DriverManager {
 
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    private static void loadInitialDrivers() {
        String drivers;
        ...
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
				// 通过ServiceLoader加载
                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
		...
        String[] driversList = drivers.split(":");
        for (String aDriver : driversList) {
            try {
                // 创建对应Driver的实例
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
            }
        }
    }
}

当我们引入mysql-connection-java.jar包时,会发现

 

也是按照标准方式来实现的自动添加Driver

3.Dubbo的SPI(基础版)

    Dubbo也有自己的SPI,我们先来看其实现,然后再总结其优势。

3.1 提供接口和实现类
package xw.demo.adaptive;
// 较上文中的不同点就是多加了一个@SPI注解
@SPI
public interface Teacher {
    void say();
}

// 三个实现类与上面示例相同,不再赘述
3.2 创建文件

    在META-INF/dubbo目录下创建名为xw.demo.adaptive.Teacher的文件,内容如下

 

注意:也可以将该文件创建在META-INF/services目录下,Dubbo也是支持的,但是为了与JDK原生的使用方式做一定的区分,特地写在META-INF/dubbo目录下

3.3 ExtensionLoader加载测试
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Teacher.class);
Teacher teacher = extensionLoader.getExtension("chinese");
teacher.say();
// 结果如下:
chinese

总结:相比较JDK的加载方式而言,Dubbo的这种方式,可以精准的加载对应的实现类,而其他我们不需要的实现类,则不需要被加载。

4.Dubbo的SPI(Wrap版)

    Dubbo在SPI的使用中,有很多包装类的实现。不动声色的就将实现类包装了一层,类似于动态代理的模式。

4.1 提供接口和实现类

    同3.1,就是多一个Wrap的实现类(共四个实现类,另外三个同上,ChineseTeacher、MathTeacher、EnglishTeacher)

package xw.demo.adaptive;
public class TeacherWrapper implements Teacher {
	// 需要有一个Teacher类型的属性
    private Teacher teacher;
    public TeacherWrapper(Teacher teacher) {
        if (teacher == null) {
            throw new IllegalArgumentException("teacher == null");
        }
        this.teacher = teacher;
    }

    @Override
    public void say() {
        System.out.println("start wrap...");
        teacher.say();
    }
}

    并将该实现类添加到上面的xw.demo.adaptive.Teacher文件中,如下

 

4.2 ExtensionLoader加载测试
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Teacher.class);
Teacher teacher = extensionLoader.getExtension("chinese");
teacher.say();

// 结果如下:
start wrap...
chinese

与3.3的测试结果有所不同,这里多了TeacherWrapper类的一层封装,所以对ChineseTeacher.say()进行调用时,先打印了封装类的输出语句。

Dubbo有很多关于封装类的使用场景,例如关于Protocol的封装类

 

ProtocolFilterWrapper、ProtocolListenerWrapper,这两个类在对外提供服务时会用到。

5.Dubbo的SPI(Adaptive方式)

    相较于3这种简单的使用方式,Dubbo源码中更常使用的是Adaptive方式,我们先来看下示例。

5.1 提供接口和实现类
package xw.demo.adaptive;
@SPI("chinese")
public interface Teacher {

    void say();

    /**
     * 添加一个方法,入参必须含有URL,必须有@Adaptive注释
     * @param url
     */
    @Adaptive
    void teach(URL url);
}

// 实现类
public class ChineseTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("chinese");
    }

    @Override
    public void teach(URL url) {
        System.out.println("teach chinese");
    }
}

public class MathTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("math");
    }

    @Override
    public void teach(URL url) {
        System.out.println("teach math");
    }
}

public class EnglishTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("english");
    }

    @Override
    public void teach(URL url) {
        System.out.println("teach english");
    }
}
5.2 创建文件

    同3.2,不再赘述

5.3 ExtensionLoader加载测试

5.3.1 测试情况一

Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
teacher.teach(url);

// 结果为
teach chinese

5.3.2 测试情况二

Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
// url有所不同
URL url = URL.valueOf("test://localhost/test?teacher=english");
teacher.teach(url);

// 结果为
teach english

5.3.3 测试情况三

// 给MathTeacher添加@Adaptive注解
@Adaptive
public class MathTeacher implements Teacher {
    @Override
    public void say() {
        System.out.println("math");
    }

    @Override
    public void teach(URL url) {
        System.out.println("teach math");
    }
}

// 测试代码
Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
// 两种url都可以
URL url = URL.valueOf("test://localhost/test?teacher=english");
// URL url = URL.valueOf("test://localhost/test");
teacher.teach(url);

// 结果为
teach math

总结:Adaptive的这种方式在Dubbo中使用较为频繁。通过这三种测试结果我们可以总结出:

1.当在实现类上指定了@Adaptive时,其级别最高,ExtensionLoader.getAdaptiveExtension()默认就返回该实现类

2.当在URL中指定了条件(本例中为teacher=english),则ExtensionLoader.getAdaptiveExtension()会返回条件中指定的实现类

3.当没有任何指定时,则使用默认值,也就是在接口的@SPI注解中指定的(本例为chinese)

注意:总结2中的条件,这个teacher是怎么来的呢?实际就是我们接口的小写之后的字母(slow(Teacher))。

如果接口是多字母组成的会怎么办呢?例如ExtAdaptive,那么不同字母之间就用.来分割,最终变成ext.adaptive

6.Dubbo SPI源码解析

    Dubbo的SPI实现了这么多层次的使用,源码也是比较有意思的,我们一起来看下

6.1 ExtensionLoader.getExtensionLoader()解析
public class ExtensionLoader {
    private static final ConcurrentMap> EXTENSION_LOADERS = new ConcurrentHashMap(64);
    private static final ConcurrentMap type;

	public static  ExtensionLoader getExtensionLoader(Class type) {
        ...
        ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            // 直接创建对应类型的ExtensionLoader
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
            loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    // 构造方法
    private ExtensionLoader(Class type) {
        // type即为本例中的SpiDemoService
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
}

ExtensionLoader.getExtensionLoader()没有太多花哨的东西,就是设置了ExtensionLoader的基本属性

6.2 ExtensionLoader.getExtension(name)方法分析(先分析简单版本)
public class ExtensionLoader {
	// extensionLoader.getExtension(name) 方法分析
    public T getExtension(String name) {
        ...
        final Holder 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) {
        // getExtensionClasses()方法很重要,我们先来分析这个方法
        // getExtensionClasses()返回的已经是 name->实现类的一个map了
        // 所以在这里直接通过name找到了对应的实现类
        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);
            }
            // set方法依赖注入
            // 这里可以通过spring获取bean的方式或SPI的方式注入属性
            injectExtension(instance);
            Set> getExtensionClasses() {
        Map> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map getAdaptiveExtensionClass() {
        // 这里与5.2中的过程一样的,中间的过程不再赘述,最终还是到达loadClass()方法,我们直接分析与上面不同的分支
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    // loadClass()
    private void loadClass(Map clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            ...
        }
        // 见4.3.3 测试情况三,当类上有@Adaptive注解时,直接将当前该类赋值给 ExtensionLoader.cachedAdaptiveClass属性
        // 后续返回getAdaptiveExtensionClass()方法时,会看到,cachedAdaptiveClass不为空,则会直接将当前类返回回去。所以带有@Adaptive注解的实现类优先级最高
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
            // 什么样的才是wrapperClass,把当前接口当做构造函数的入参就叫包装类
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            // 最终获取对应的class信息(本例中就是在这里获取)
            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, overridden);
                }
            }
        }
    }
}

那么问题来了,针对4.3.1 4.3.2的这两种测试情况怎么看这个代码呢?我们回到getAdaptiveExtensionClass()方法

public class ExtensionLoader {
	private Class getAdaptiveExtensionClass() {
        getExtensionClasses();
        // 非4.3.3情况,则cachedAdaptiveClass会为空
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 逻辑走到这
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    private Class createAdaptiveExtensionClass() {
        // 通过javassist的方式动态生成其代理类,具体我们不再深入,我们来看下生成的代理类的具体代码
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
}

通过javassist的方式生成的动态代理类代码(针对本例而言)

public class Teacher$Adaptive implements xw.demo.adaptive.Teacher {
    public void teach(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg0;
        // 供调用方手动设置参数,参数名为teacher,具体使用方式参见4.3.2 测试情况二
        // 默认值为chinese,是由Teacher接口@SPI("chinese")指定的
        String extName = url.getParameter("teacher", "chinese");
        if (extName == null) {
            throw new IllegalStateException("Failed to get extension (xw.demo.adaptive.Teacher) name from url (" + url.toString() + ") use keys([teacher])");
        }
        // 最终回归到原生的调用方式getExtension(name),类似于3.3的方式
        xw.demo.adaptive.Teacher extension = (xw.demo.adaptive.Teacher) ExtensionLoader.getExtensionLoader(xw.demo.adaptive.Teacher.class).getExtension(extName);
        extension.teach(arg0);
    }

    public void say() {
        throw new UnsupportedOperationException("The method public abstract void xw.demo.adaptive.Teacher.say() of interface xw.demo.adaptive.Teacher is not adaptive method!");
    }
}
总结:

我们再把上面的获取代理类的顺序重复一下:

1.当在实现类上指定了@Adaptive时,其级别最高,ExtensionLoader.getAdaptiveExtension()默认就返回该实现类

2.当在URL中指定了条件,则ExtensionLoader.getAdaptiveExtension()会返回条件中指定的实现类

3.当没有任何指定时,则使用默认值,也就是在接口的@SPI注解中指定的

关注
打赏
1655041699
查看更多评论
立即登录/注册

微信扫码登录

0.0395s