阅读 125

Alibaba dubbo SPI机制全解析

基于较老版本DUBBO,只供熟悉dubbo设计使用

什么是SPI

概念

什么是SPI: 全称(service provider interface),是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如JDBC、日志框架都有用到;简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于应用方来说,只需要集成对应厂商的插件,就可以完成对应规范的实现机制。 形成一种插拔式的扩展手段。

实现流程

实现SPI,就需要按照SPI本身定义的规范来进行配置,SPI规范如下

  1. 需要在classpath下创建一个目录,该目录命名必须是:META-INF/services
  2. 在该目录下创建一个properties文件,该文件需要满足以下几个条件
    1. 文件名必须是扩展的接口的全路径名称
    2. 文件内部描述的是该扩展接口的所有实现类
    3. 文件的编码格式是UTF-8
  3. 通过java.util.ServiceLoader的加载机制来发现

缺点

  1. JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源
  2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因

DUBBO SPI源码解析

JAVA SPI实现

定义接口项目 image.png 实现工程

  1. 依赖接口项目
  2. 实现接口
  3. 根据SPI规范,在META-INF/servcies文件夹下定义文件,文件名为接口全路径,文件内容为实现类全路径

image.png 怎么使用 image.png

核心点在于 META-INF/services下面配置的文件

两个核心注解 @SPI @Adaptive

SPI

/**
 * 扩展点接口的标识;
 * 如果被这个注解修饰的接口,代表在三个配置文件地址中必有全类名文件,文件中以key=value的形式记录这个接口的所有扩展点
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * 缺省扩展点名。
     * 这个值会用于生成动态扩展点时的兜底默认值
     */
	String value() default "";

}
复制代码

Adaptive

/**
 * 在{@link ExtensionLoader}生成Extension的Adaptive Instance时,为{@link ExtensionLoader}提供信息。
 * 
 * @author ding.lid
 * @export
 * 
 * @see ExtensionLoader
 * @see URL
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    
    /**
     * 1.方法修饰:
     * 1.1当这个value的值为xxx或者protocol时,就会从URL上取出url.getParameter(xxx)或者url.getProtocol的值,然后找到这个值对应的扩展点中的被这个注解修饰的同名方法作为默认实现
     * for example:
     * RegistryFacotry中的registry方法:
     * <pre>
     *     @Adaptive({"protocol"})
     *     Registry getRegistry(URL url)
     * </pre>
     * @Adaptive的值为protocol,所以会从url.getProtocol中取出值,如果url.getProtocol=dubbo,那么当我们获取动态扩展点时,
     * 这个getRegistry的动态实现就是dubboRegistryFactory的getRegistry实现
     *
     * 其实就是将方法的实现与URL动态绑定
     *
     * 1.2 那么如果这个value为空数组呢?空数组的话,会获取接口名并且将其进行切割转换,比如 com.alibaba.dubbo.xxx.YyyInvokerWrapper 会被转换为yyy.invoker.wrapper
     * 然后 url.getParameter(yyy.invoker.wrapper)获取值,然后找到这个值对应的扩展点中的被这个注解修饰的同名方法作为默认实现
     *
     * 1.3 如果上面两步都没有获取到值呢?就会读取接口中SPI注解中的默认值,然后找到这个值对应的扩展点中的被这个注解修饰的同名方法作为默认实现
     *
     * 类修饰:
     * 如果一个类上有@Adaptive注解。那么他就是它的SPI接口的默认扩展点,在获取动态扩展点时会得到这个默认扩展点;
     * 比如:AdaptiveExtensionFactory af = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
     * @return
     */
    String[] value() default {};
    
}
复制代码

DUBBO SPI源码

DUBBO对SPI做了优化

  1. 配置文件的位置改为 META-INF/dubbo 或者 META-INF/dubbo/internal 或者 META-INF/services
  2. 文件名依旧是接口全路径,但是内容不一样,变为key=value的形式

目标源码

ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
复制代码

测试DEMO,使用Protocal作为样例

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
复制代码

Protocal代码

@SPI("dubbo")
public interface Protocol {
    
    /**
     * 获取缺省端口,当用户没有配置端口时使用。
     * 
     * @return 缺省端口
     */
    int getDefaultPort();

    /**
     * 暴露远程服务:<br>
     * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
     * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
     * 
     * @param <T> 服务的类型
     * @param invoker 服务的执行体
     * @return exporter 暴露服务的引用,用于取消暴露
     * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务:<br>
     * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
     * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
     * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
     * 
     * @param <T> 服务的类型
     * @param type 服务的类型
     * @param url 远程服务的URL地址
     * @return invoker 服务的本地代理
     * @throws RpcException 当连接服务提供方失败时抛出
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 释放协议:<br>
     * 1. 取消该协议所有已经暴露和引用的服务。<br>
     * 2. 释放协议所占用的所有资源,比如连接和端口。<br>
     * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
     */
    void destroy();

}
复制代码
  1. 拥有SPI注解,且默认值为dubbo
  2. 有@Adaptive的方法,这个注解修饰的方法会被适配器扩展

DUBBO SPI 源码基本来自于ExtensionLoader类

先看getExtensionLoader

  1. 检测传入的type是否有SPI注解
  2. 根据type从缓存中获取一个ExtensionLoader
  3. 如果没有获取到则根据type创建一个ExtensionLoader,==++并把这个type注册给ExtensionLoader++==
  4. 将type和ExtensionLoader的映射关系加入到缓存中,可以看到一个type只会有一个ExtensionLoader,是单例的;
    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        // 检测type是否为空
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 检测是否是SPI注解
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        // 根据类型从缓存中获取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;
    }
    // 判断是否有SPI注解
    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }
    // ExtensionLoader构造方法
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // 这里加载到的objectFacotry为AdaptiveObjectFactory
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
复制代码

获得ExtensionLoader之后我们来看getAdaptiveExtension方法,这个方法最后会获得一个'Xxx$Adaptive'的适配扩展点或者获得默认扩展点

    public T getAdaptiveExtension() {
        // 从内存缓存中获取
        Object instance = cachedAdaptiveInstance.get();
        // 双重检查锁
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    // 再次从内存中获取
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        // 获取不到,创建一个并放入到内存中
                        try {
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }
复制代码

从内存缓存中获取,如果没有获取到,那么就会调用createAdaptiveExtension创建一个,并加入到缓存中

我们看createAdaptiveExtension方法

    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }
复制代码

可以看到这里有两个步骤

  1. getAdaptiveExtensionClass().newInstance() 获得适配器扩展点的CLASS,然后反射创建一个适配器扩展点
  2. 将1创建的对象,作为参数,调用injectExtension注入

先看getAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
        // 加载META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/三个文件夹下面的key和value的映射关系到内存
        // 并且如果有默认拓展点的话注册默认扩展点;
        getExtensionClasses();
        // 判断是否注册了默认扩展点,有,则返回,没有则创建动态扩展点
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
复制代码
  1. getExtensionClasses加载META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/三个文件夹下面的key和value的映射关系到内存,并且如果有默认拓展点的话注册默认扩展点;
    1. 什么是默认扩展点?就是传入的type的一个实现类,且这个实现类类上有一个@Adaptive的注解
  2. 判断当前的ExtensionLoader类是否已经注册了默认扩展点class
    1. 已经有了,直接返回
    2. 没有,调用createAdaptiveExtensionClass创建一个

先看getExtensionClasses方法,可以看到核心是loadExtensionClasses

private Map<String, Class<?>> getExtensionClasses() {
        // 先从缓存中取
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 取不到,加载,并加入到缓存中
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
	}
	
// 加载META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/三个文件夹下面的key和value的映射关系到内存
private Map<String, Class<?>> loadExtensionClasses() {

        // 从type中读取注解SPI中的值,比如我们传入的是Protocal.class,所以此时type是Protocal.class
        // 它的SPI注解的默认值是dubbo
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                // 将注解中的默认的值注册给当前的Extensionloader
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        // 从SIP配置文件中加载SPI相关的CLASS,并且寻找默认扩展点
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

    // 存放SPI配置文件的目录地址
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
复制代码
  1. 先从缓存中取
  2. 取不到,加载配置文件获得
    1. 从type中读取注解SPI中的值,比如我们传入的是Protocal.class,所以此时type是Protocal.class,它的SPI注解的默认值是dubbo
    2. 将注解中的默认的值注册给当前的Extensionloader
    3. 从SIP配置文件中加载SPI相关的CLASS(定义的扩展点),==++可以看到约定的目录地址:META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/++==
    4. 将目录地址中的对应的配置文件的内容加载到内存中,比如Protocal.class,找的就是名为com.alibaba.dubbo.rpc.Protocol的文件,最终我们在META-INF/dubbo/internal/中找到,可以看到它的内容为key=value的形式
    5. 三个目录下的所有的这种配置文件中的映射关系,都会被载入到内存中
    6. 载入的过程中还会寻找默认扩展点,有,注解到当前的cachedAdaptiveClass中
  3. 加入缓存中

我们细看loadFile

  1. 根据三个文件目录地址+传入的类名拼接成对应的文件地址:META-INF/services/com.alibaba.dubbo.rpc.Protocol
  2. 读取文件中的内容,一行一行读取,每一行作为一个class
    1. 判断class是否有@Adaptive注解,如果有,则将其注册为默认扩展点(cachedAdaptiveClass)
    2. 如果不是默认扩展点,查看是否是包装类(通过构造是否含有type):参考Protocol和ProtocolListenerWrapper,ProtocolFilterWrapper
      1. 有,则加入到包装类集合中,++这个包装类集合会用于当获取指定扩展点时对结果的两层包装++
      2. 没有,则加入到类名和class的映射关系的缓存中
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        // 根据type获得文件
        // 比如:META-INF/services/com.alibaba.dubbo.rpc.Protocol
        String fileName = dir + type.getName();
        try {
            // 读取文件内容
            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 url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                // 每一行生成一个class对象
                                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) {
                                            name = line.substring(0, i).trim();
                                            line = line.substring(i + 1).trim();
                                        }
                                        if (line.length() > 0) {
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            if (! type.isAssignableFrom(clazz)) {
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class " 
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            // 判断这个class是否是带有@Adaptive注解,如果有就是默认扩展点,注册给cachedAdaptiveClass
                                            if (clazz.isAnnotationPresent(com.alibaba.dubbo.common.extension.Adaptive.class)) {
                                                if(cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {
                                                
                                                try {
                                                    // 查看当前读取的这一行生成的这个类是否有包含这个type的构造方法
                                                    clazz.getConstructor(type);
                                                    // 如果有,则说明是这个type的包装类,加入包装类集合中
                                                    // 比如 type为Protocol.class时,会有ProtocolFilterWrapper和ProtocolListenerWrapper满足条件
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    // 当前读取的这一行生成的这个类没有包含这个type的构造方法
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                        com.alibaba.dubbo.common.extension.Activate activate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                // 加入到class和类名的映射关系中
                                                                //(com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol 和 injvm)
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            } else if (c != clazz) {
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
复制代码

包装类举例

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

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

举例文件内容: com.alibaba.dubbo.rpc.Protocol文件中的内容

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol

#包装类
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper

mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

复制代码

getExtensionClasses结束后,我们继续看createAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
        // 加载META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/三个文件夹下面的key和value的映射关系到内存
        // 并且如果有默认拓展点的话注册默认扩展点;
        getExtensionClasses();
        // 判断是否注册了默认扩展点,有,则返回,没有则创建动态扩展点
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
复制代码
  1. 加载所有扩展点的CLASS,并且如果能到找到@Adaptive注解修饰的默认扩展点,将其注册为cachedAdaptiveClass
  2. 如果注册了cachedAdaptiveClass,那么将其返回,如果没有,则创建一个动态扩展点

生成动态扩展点

private Class<?> createAdaptiveExtensionClass() {
        // 生成适配器扩展点代码
        String code = createAdaptiveExtensionClassCode();
        // 获取类加载器
        ClassLoader classLoader = findClassLoader();
        // 编译代码,生成class文件
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
复制代码
  1. 生成适配器扩展点代码
    • 必须有@Adaptive修饰的方法,不然会生成失败
  2. 获取类加载器
  3. 编译代码,生成class文件

生成代码的过程

private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 完全没有Adaptive方法,则不需要生成Adaptive类
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
        
        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            Class<?>[] pts = method.getParameterTypes();
            Class<?>[] ets = method.getExceptionTypes();

            // 获取方法上的@Adaptive注解
            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // ============================= 寻找URL参数START =============================
                // 有类型为URL的参数
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);
                    
                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                    code.append(s);
                }
                // 参数没有URL类型
                else {
                    String attribMethod = null;
                    
                    // 找到参数的URL属性,比如参数只有一个Invoker,那么就找到Invoker类的url属性
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if(attribMethod == null) {
                        throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                        		+ ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }
                    
                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                    code.append(s);
                }
                // ============================= 寻找URL参数end =============================
                
                // ============================= 寻找@Adaptive注解的值START =============================
                // 获取方法上的@Adaptive注解的值,这个值决定了从URL上取用哪个参数的值来决定该方法的实现扩展点
                String[] value = adaptiveAnnotation.value();
                // 方法上的@Adaptive注解的值为空,则使用扩展点接口名分隔来做@Adaptive注解的值,比如Protocol会变成{"protocol"},YyyXxxx会变成{"yyy.xxxx"}
                if(value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if(Character.isUpperCase(charArray[i])) {
                            if(i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        }
                        else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }
                // ============================= 寻找@Adaptive注解的值END =============================
                
                // ============================= 封装异常START =============================
                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }
                // ============================= 封装异常END =============================

                
                // ============================= 封装寻找实现扩展点逻辑START =============================
                // cachedDefaultName是接口SPI注解中的值,在loadExtensionClasses方法中封装
                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    // 定义extName的核心位置,从URL上获取核心参数,根本上说明DUBBO是URL驱动
                    // extName用于获取实现类
                    if(i == value.length - 1) {
                        if(null != defaultExtName) {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                        }
                        else {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    }
                    else {
                        if(!"protocol".equals(value[i]))
                            if (hasInvocation) 
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                		"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);
                
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);
                
                // ============================= 封装寻找实现扩展点逻辑END =============================
                
                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }
            
            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i ++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i ++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(pts[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }
复制代码

从获取extName的过程,可以看到DUBBO为什么是基于URL驱动的。

生成的Protocal扩展点代码为

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    // 引用方法
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
    
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        
        // 从URL中获取协议,如果协议为空,默认走dubbo
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
            
        // 获取扩展点实现类,默认获取DubboXxx的两层包装类
        com.alibaba.dubbo.rpc.Protocol extension = 
        (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        
        // 调用扩展点实现类的refer方法
        return extension.refer(arg0, arg1);
    }

    // 输出方法
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
    
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
            
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        // 从URL中获取协议,如果协议为空,默认走dubbo
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
         // 获取扩展点实现类,默认获取DubboXxx的两层包装类
        com.alibaba.dubbo.rpc.Protocol extension = 
        (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        
         // 调用扩展点实现类的export方法
        return extension.export(arg0);
    }
}
复制代码

Xxx$Adaptive的主要功能

  1. 为Xxx接口实现动态适配器扩展,什么叫做动态适配器扩展?
    • 就是为Xxx接口中的@Adaptive注解修饰的方法做出动态实现,动态实现实际上就是从Xxx的多个扩展点中动态找到某一个实现类来实现这个方法
      • 怎么找?根据@Adaptive注解中的值来从url上取下指定的参数的值
        • 比如@Adaptive的值为protocol,那么就取下url.getProtocol的值
        • 如果@Adaptive的值不等于protocol,比如是server,就取下url.getParameter("server")的值
        • 如果@Adaptive注解为空数组,那么就转换接口扩展点的名字来作为@Adaptive的值,转换eg:接口名为XxxYyy,就是xxx.yyy,那么就会是url.getParameter("xxx.yyy")的值
        • 如果url.getProtocol或者url.getParameter等于空。那么就会是取SPI注解中的值
          • eg: Protocol$Adaptive: url.getProtocol == null ? "dubbo":url.getProtocol
      • 找到值后然后呢?
        • 根据这个值,作为key,在Xxx的扩展配置文件中找到对应的value即为扩展点全类名,这个扩展点中对这个方法的实现就是目标方法
        • 比如com.alibaba.dubbo.rpc.Protocol,里面的内容包括了:dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol,就是通过这种key=value的形式找到了对应的类
      • 可以看到dubbo是基于URL驱动的,从URL上的相关的配置就可以找到动态找到对应的实现类;
        • dubbo://172.16.1.221/com.blackcookie.duboo.BCHelloService?Fanyhost=Dtrue&application=Ddubbo-server&dubbo=D2.5.3&interface=Dcom.blackcookie.duboo.BCHelloService&methods=DsayHello&owner=Dxuxudong&pid=D9514&side=Dprovider&timestamp=D1545204055277
      • 如果@Adaptive注解修饰的方法的参数中没有URL呢?那也会从参数的属性中找到URL,反正一定要找到URL

生成Protocol$Adaptive之后,我们回到createAdaptiveExtension方法,继续看injectExtension

    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }
复制代码

injectExtension实现:如果一个扩展点中有一个set方法,且setXXX中XXX也是一个扩展点,实行注入

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    // 处理set方法
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        // 获取第一个参数的CLASS文件,比如setProtocol(DubboProtocol protocol)得到Protocol.class
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 从set方法从截取类型,比如setProtocol得到Protocol
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 读取META-INF/services/,META-INF/dubbo/nternal/,META-INF/dubbo/三个文件夹下面的key和value的映射关系
                            // 从这个映射关系中,根据class文件和方法名中截取的字符串,得到对应的class
                            // 这里的objectFacotry为AdaptiveObjectFactory,为什么?看ExtensionLoader的构造方法
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 调用set方法,完成注入
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
复制代码

简单来说,这个方法的作用,是为这个自适应扩展点进行依赖注入。类似于spring里面的依赖注入功能。如果setXxx中的Xxx是一个接口,那么注入的是一个动态扩展类Xxx$Adaptive,如果是一个类,那么就会注入这个类

比如下面这个类就会触发injectExtension

  1. RegistryProtocol实现Protocol接口,RegistryProtocol是一个带有SPI注解的扩展点
  2. setProtocol,Protocol本身也是一个扩展点
  3. Protocol是接口,注入的是Protocol$Adaptive
public class RegistryProtocol implements Protocol {

    private Cluster cluster;
    
    public void setCluster(Cluster cluster) {
        this.cluster = cluster;
    }
    
    private Protocol protocol;
    
    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
复制代码

总的概览图: image.png dubbo源码中,我们可以发现dubbo的源码中也会采用另一种方式,这种是指定扩展

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
复制代码

原理类似

  1. 先从SPI配置文件位置的文件里面获取得到该接口的所有的扩展点
  2. 然后根据传入的名字,从key和value的映射关系中得到对应的扩展点的class文件
  3. 完成注入
  4. 如果这个类的SPI接口的扩展点有wrapper的话,就会继续为这个class文件进行包装,比如Protocol,就会封装两层,里层是监听(listener)包装,外层是过滤(Filter)封装

区别在于,这个是直接找到对应的指定的class文件

总结:

  1. 为何要研究这个代码,通过这个源码我们发现了dubbo实现动态发现和扩展的原理:基于SPI实现,配合@Adaptive注解
    1. 当我们动态获取一个接口的扩展点时,比如ExtensionLoader.getExtensionLoader(A.class).getAdaptiveExtension();
      1. 首先A.class拥有SPI注解
      2. 我们在三个SPI配置路径下根据A.class的全路径名称可以找到A.class的所有扩展点,并寻找是否有带有@Adaptive类注解的默认扩展点
        1. 有:那么这个就是动态获取接口的扩展点的结果
          • 比如:AdaptiveExtensionFactory af = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
        2. 没有:那么就会为A.class中的带有@Adaptive注解的方法动态生成代码,最后生成一个A$Adaptive的类,并加载到JVM中,然后返回
    2. 当我们指定一个目标扩展点时,比如ExtensionLoader.getExtensionLoader(A.class).getExtension("aaa");
      1. 我们在三个SPI配置路径下根据A.class的全路径名称可以找到A.class的所有扩展点,并寻找key为aaa的扩展点
      2. 找到指定扩展点,如果A.class的扩展点中有包装扩展点,则将指定扩展点包装后返回,比如new FilterWrapper(new ListenerWrapper(aaa));

名词解释:

  1. 接口扩展点 被SPI注解修饰的类 比如Protocol
  2. 动态扩展点 Xxx$Adaptive
  3. 包装扩展点 构造函数中含有接口扩展点,比如ProtocolListenerWrapper有一个public ProtocolListenerWrapper(Protocol protocol){...}的构造
  4. 默认扩展点 类上有@Adaptive注解,比如AdaptiveRegistryFactroy就是RegistryFactroy的默认扩展点
文章分类
后端
文章标签