2、Dubbo源码系列-SPI与动态增强

245 阅读5分钟

上篇文章整体介绍了Dubbo的架构,其中也提到了,学好Dubbo的关键在于先整明白SPI原理,今天就带大家一起学习下相关设计

一、Java SPI

在正式解读Dubbo SPI之前,先简单带大家过下Java原生的SPI。

1.定于接口

public interface Rpc {
    void invoke(String method);
}

2.定义实现类

public class Dubbo implements Rpc {
    @Override
    public void invoke(String method) {
        System.out.println("Dubbo" + "_" + method);
    }
}
public class Thrift implements Rpc {
    @Override
    public void invoke(String method) {
        System.out.println("Thrift" + "_" + method);
    }
}

3.定义SPI声明

image.png 在项目的Resources目录下,定于SPI声明文件

4.调用SPI

image.png

可以发现,RPC对应的两个实现类,此时都已经被加载到内存里。

5.实现原理

image.png

优点

  • 解藕,应用程序可以根据实现业务情况启用或替换组件 缺点
  • 加载实现类的时候会全部加载,不管你是否会不会用到
  • 只能通过迭代器获取,无法通过关键字获取
  • 非线程安全,多线程情况下可能会有线程安全问题
  • 不会有好的提示扩展点加载失败异常

二、Dubbo增强SPI

Dubbo扩展点机制是基于JDK标准SPI扩展机制增强而来,解决了JDK SPI以下问题

  • 不用一次性加载所有扩展点实现
  • 如扩展点加载失败,不会有好的提示异常
  • 增加了对扩展点IOC和AOP的支持,一个扩展点可以通过setter方法注入其他扩展点

1.服务提供者

public class Application {
    public static void main(String[] args) throws Exception {
        ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());

        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        bootstrap
                .application(new ApplicationConfig("dubbo-demo-api-provider"))
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                .service(service)
                .start()
                .await();
    }
}

2.扩展点切入

image.png

以Protocol为例,服务发布者通过ServiceConfig进行服务的发布,其内部会通过以下方法获取Protocol实例

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

查看Protocol代码发现,其明显是个扩展点,默认注入实现为“dubbo”

@SPI("dubbo")
public interface Protocol {
    ... ...
}

3.ExtensionLoader分析

ExtensionLoader类似JDK的ServiceLoader,作用也是获取到对应的SPI实现类,不过这里getAdaptiveExtension获取到的最终是Protocol的适配类,后面会讲是怎么动态生成的。

  • getExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    ...省略校验 ...
    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中,每个扩展接口,都会对应一个ExtensionLoader对象,内部通过一个map进行缓存。

  • getAdaptiveExtension
public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        ... 省略校验 ...
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                try {
                    instance = createAdaptiveExtension();
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    ... 异常处理 ...
                }
            }
        }
    }

    return (T) instance;
}

可以看到,先从内部的cache中获取适配类,如果不存在,则调用createAdaptiveExtension方法进行创建。

  • createAdaptiveExtension
private T createAdaptiveExtension() {
   return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}

首先调用getAdaptiveExtensionClass.newInstance()方法获取到适配器class,然后调用injectExtension进行扩展点相互依赖注入

  • getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

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

首先调用getExtensionClasses获取该接口的所有实现类,然后调用createAdaptiveExtensionClass方法创建具体的适配器对象

  • getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
    // 拿Protocol来说,这里的cacheDefaultName就是dubbo
    cacheDefaultExtensionName();

    // 然后去 META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal目录下去加载具体的实现类
    Map<String, Class<?>> extensionClasses = new HashMap<>();
    // internal extension load from ExtensionLoader's ClassLoader first
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);

    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;
}

首先获取到cacheDefaultName,然后分别去META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal目录下去加载具体的实现类

image.png 这里可以看下Dubbo的注入SPI文件格式,和Java原生的其实也有区别。

  • createAdaptiveExtensionClass
private Class<?> createAdaptiveExtensionClass() {
    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);
}

通过字节码生成适配器class对象并返回,然后通过newInstance方法生成对应实例

image.png 可以看到最终生成的是“Protocal$Adaptive”类型的对象(暂时不展开)

  • injectExtension
private T injectExtension(T instance) {

    try {
        for (Method method : instance.getClass().getMethods()) {
            ... 省略校验...
            try {
                // 根据setter属性获取对应的属性名,比如getUser,则获取到的user
                String property = getSetterProperty(method);
                // 该属性是否是扩展点实现
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // setter注入
                    method.invoke(instance, object);
                }
            } catch (Exception e) {

            }

        }
    } catch (Exception e) {
    }
    return instance;
}

进行扩展相互依赖属性注入

  • 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();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

根据扩展类的名称,找到对应的实现类

4.小结

image.png 以上就是整体的调用流程,涉及到的方法调用比较多

核心点在于,Dubbo会从以下四个目录读取文件

  • META-INF/dubbo/internal/
  • META-INF/dubbo/
  • META-INF/services/
  • META-INF/dubbo/external/

文件名为接口的全限定名,内容为键值对,键为短名称(即SPI注解里写的字符串,如“dubbo”),值为实现类。

读取到对应的配置后,会创建对应的SPI类,同时如果关联了多个Wrapper类,也会一一生成。最后生成动态“Protocal$Adaptive”适配对象。

当我们代码中调用protocal.export的时候,在没有扩展点包装的时候,调用的就是DubboProtocol的export对象 。

但实际使用过程中,会使用ProtocolFilterWrapper、ProtocolListenerWrapper等Wrapper类对DubboProtocol对象进行增强,下面就让我们看看,Wrapper类是如何进行增强的。

三、扩展点自动包装

查看ProtocolListenerWrapper源码,protocol实际类型为DubboProtocol。

public class ProtocolListenerWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    ... ...
}

第二次使用ProtocolFilterWrapper对ProtocolListenerWrapper进行封装,protocol为ProtocolListenerWrapper

1 包装类收集

回到上一讲,getExtensionClasses里会调用loadDirectory加载扩展接口的所有实现类对象,同时会对包装类Wrapper也进行了收集,让我们看下loadDirectory -> loadDirectory -> loadClass方法

    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)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
           ... ...
        }
    }

加载类的时候,会调用isWrapperClass方法对clazz进行判断,如果当前是Wrapper类 ,则会进行缓存收集

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

通过判断当前clazz构造函数是否包含当前被加载的类,来判断clazz是否是包装类。那么对于缓存的Wrapper类是如何使用的呢?

2 包装类使用

    private T createExtension(String name) {
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            ... ...
        }
    }

在调用getExtension获取对应的实现类时,底层会调用createExtension来创建的实现类 ,如果当前存在Wrapper类的话,则会遍历使用injectExtension一层一层对扩展类进行增强。

image.png debug也可以看到,最终Protocol对应两个增强类,先由ProtocolListenerWrapper对DubboProtocol进行增强,然后使用ProtocolFilterWrapper对ProtocolListenerWrapper进行增强。

四、Dubbo动态编译

1.Adaptive实现

前文也提到过,在获取到Protocol的SPI实现类DubbolProtocol后,Dubbo会生成一个Protocol$Adaptive对象,其作用实际上是个适配器,封装了Protocol的具体实现类,查看源码如下(可以通过阿里的Arthas来查看)

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        ... ...
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

内部调用ExtensionLoader对应方法,获取到SPI具体实现,其实获取到的就是增强后的类,然后执行其export方法进行服务引用

2.适配器原理

Dubbo提供了Compiler接口,用来实现动态编译功能,其本身也是个SPI。提供了两种实现,分别是默认实现JavassistCompiler以及JdkCompiler

@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

再次回顾前文的动态创建方法

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

可以看到,也是通过ExtensionLoader来加载Compiler的具体SPI实现,然后动态的生成代理类。所以,Compiler类一定是最先加载的,然后才能去创建其他的适配器。

五、小节

本篇带大家回顾了Java原生SPI的工作原理及缺点,同时分析了Dubbo的SPI流程以及动态加载原理,如果看到这觉得有收获的话,不妨点个赞,有什么问题也可以在评论区讨论交流~