Dubbo学习记录(四)----SPI可扩展机制

Dubbo SPI可扩展机制

上一篇解释了JDK自带的SPI机制,以及SPI的优缺点,以及使用, 这次写点Dubbo的SPI机制;

Dubbo SPI 举例

        ExtensionLoader<Protocol> protocolExtensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
        Protocol dubboProtocol = protocolExtensionLoader.getExtension("dubbo");
        Protocol httpProtocol = protocolExtensionLoader.getExtension("http");
        System.out.println(dubboProtocol);
        System.out.println(httpProtocol);

这个就是Dubbo常见的写法, protocolExtensionLoader.getExtension(“dubbo”)获取dubbo对应的Protocol扩展点,
即Dubbo实现了dubbo协议, 这个协议实现类实现了Protocol接口, 同理http也是如此;

ExtensionLoader#getExtensionLoader(type)

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
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;
    }
  1. ExtensionLoader类有个ConcurrentHashMap属性,名为EXTENSION_LOADERS , 用来获取存放某个接口类型的
    ExtensionLoader实例
  2. 调用getExtensionLoader的时候,首先从EXTENSION_LOADERS获取加载器, 如果获取不到,就根据
    接口类型常见一个加载器,放入EXTENSION_LOADERS容器中
  3. 使用ConcurrentHashMap保证了线程安全;

ExtensionLoader

ExtensionLoader表示某个接口的扩展点加载器, 可以用来加载某个扩展点实例。

public class ExtensionLoader<T> {

    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/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    private final Class<?> type;

    private final ExtensionFactory objectFactory;

    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    private volatile Class<?> cachedAdaptiveClass = null;
    private String cachedDefaultName;
    private volatile Throwable createAdaptiveInstanceError;

    private Set<Class<?>> cachedWrapperClasses;

    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());   // AdaptiveExtensionFactory
    }
 }   

有几个很重要的属性:

  1. SERVICES_DIRECTORY = “META-INF/services/”
    传统JDK自带的SPI扫描接口类型目录;
  2. DUBBO_DIRECTORY = “META-INF/dubbo/”
    Dubbo中自带实现的SPI扫描接口类型目录之一;
  3. DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + “internal/”
    Dubbo中自带实现的SPI扫描接口类型目录之一;
  4. EXTENSION_LOADERS
    存放各个接口类型对应的加载器;
  5. EXTENSION_INSTANCES
    存放 各个实现了接口类型的实现类实例, 是一个ConcurrentHashMap;
  6. Class<?> type
    代表加载器的接口类型
  7. ExtensionFactory objectFactory
    扩展点共享(对象工厂) , 可以获得某个对象;

ExtensionLoader与ExtensionFactory的区别

  1. ExtensionLoader最终获得的对象是Dubbo SPI机制产生的。
  2. ExtensionFactory最终得到的对象可能是Dubbo SPI机制产生的, 也可能是从Spring容器获得的对象;

三个常用方法

  1. getExtension(“dubbo”) : 获取某个接口类型的实现类;
  public T getExtension(String name) {
        // 获取默认扩展类
        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;
    }
  1. getAdaptiveExtension(): 表示获取一个自适应的扩展点实例;每个自适应的扩展点实例创建后,都会放入ConcurrentHashMap缓存cachedAdaptiveInstance;
    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;
    }
  1. getActivateExtension(URL url, String[] values, String group)
自适应扩展实例

本质上就是当前接口类型的一个代理对象;

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getExtension("dubbo");

调用上述代码的时候,会得到一个Dubboprotocol的实例对象,在getExtension()方法中, Dubbo会对DubboProtocol对象进行依赖注入(对应Spring的自动填充),就是自动给属性复制, 属性的类型是一个接口(记为A),这个时候, 容器中可能会有多个A的实现类,Dubbo并不知道要注入那个实现类,就会先赋值一个A接口的自适应扩展点实例, 也就是A接口的一个代理对象;

而当A接口的代理对象被真正调用时, 才会结合URL信息找到真正的A接口对象的扩展点实例进行调用;

在这里插入图片描述

dubbo解决这个问题的方法是: 调用方法上 加上扩展实现类的URL路径, 然后代理实例可以根据URL中的信息找到A接口具体的实现类对象, 进而执行实现类的方法methodB(url);
在这里插入图片描述

getExtension(String name)

在调用getExtension去获取一个扩展点实例后,会对实例进行缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。

createExtension(String name)
 private T createExtension(String name) {
        // 获取扩展类  {name: Class}  key-Value    接口的所有实现类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }

        try {
            // 实例缓存
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 创建实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // 依赖注入 IOC
            injectExtension(instance);

            // AOP
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // new CarWrapper(instance)---CarWrapper实例
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));  
                }
            }

            return instance;
        } catch (Throwable t) {
        }
    }

在getExtension方法中,调用了如果实例不存在,会调用createExtension方法创建 扩展点实例;

  1. 根据name找到对应的扩展点实现类的Class信息;
  2. 根据Class信息,通过反射生成实例,把实现类Class和对应生成的实例放入缓存;
  3. 对生成出来的是进行依赖注入(给实例的属性进行赋值);
  4. 依赖注入后的实例进行AOP, 当前接口类的所有的Wrapper全部一层一层包裹在实例对象上,没包裹个Wrapper后,也会对Wrapper对象进行依赖注入;
  5. 返回最终的Wrapper对象;
    在这里插入图片描述
getExtensionClasses
  1. 用来加载接口类型所有的扩展点实现类Class信息;
  2. 会将所有的扩展点实现类,放入cachedClasses缓存Map中,下次获取从cachedClasses中获取;
 private T createExtension(String name) {
        // 获取扩展类  {name: Class}  key-Value    接口的所有实现类
        Class<?> clazz = getExtensionClasses().get(name);
        //...
}
    /**
     * 加载当前ExtensionLoader对象中指定的接口的所有扩展
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        //双端检索机制:线程安全, 并且防止重复生成对象,做无用功;
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses(); // 加载、解析文件 Map
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses

加载默认的扩展类;
从多个路径去扫描所有的扩展点实现类;

    /**
     * synchronized in getExtensionClasses
     * */
    private Map<String, Class<?>> loadExtensionClasses() {
        // cache接口默认的扩展类
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        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;
    }

cacheDefaultExtensionName()
@SPI(value = "com.tuling.Product")
public interface Product {
    void use();
}
  • @SPI的作用
    注解的value属性值表示默认使用某个扩展点实现类的类路径信息;
    private void cacheDefaultExtensionName() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if (names.length > 1) {
                throw new IllegalStateException("...");
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
  • 如果使用了@SPI注解并设置value,则duboo在cacheDefaultExtensionName中会加载value值作为默认的扩展点实现类;
loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type)
  1. 根据目录和类型 拼凑出 文件名
    格式: /WEB-INF/services/com.tuling.product
  2. 获取类加载器
    • findClassLoader最终会从Thread的上下文中拿类加载器,默认情况,线程上下文放的应用类加载器;
    • 如果拿不到加载器, 就会使用系统类加载,去加载系统资源;
    • 由于可能存在多个模块, 每个模块都有自己的 /WEB-INF/services/com.tuling.product, 最终都会加载出来, 放入Enumeration<java.net.URL>中;
  3. 遍历,加载资源;
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            // 根据文件中的内容得到urls, 每个url表示一个扩展    http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 遍历url进行加载,把扩展类添加到extensionClasses中
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
        //...
        }
    }
loadResource(extensionClasses, classLoader, resourceURL)
  1. 负责解析加载文件成IO流
  2. 逐行读取每个文件的内容
    2.1 如果以#开头, 则表示为注释, 就不加载这行,
    2.2 如果不是以#开头, #在中间, 就读取#之前的内容;
  3. 处理每一行的内容line
    3.1 获取“=”出现的位置, "="前的内容为name, "="后的内容为类路径信息;
    3.2 加载类,并添加到extensionClasses中
 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // 加载类,并添加到extensionClasses中
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                        }
                    }
                }
            }
        } catch (Throwable t) {
        }
    }

小结:

  1. loadResource方法就是完成对文件内容的解析,按行进行解析,会解析出"=“两边的内容,”="左边的内容就是扩展点的name,右边的内容就是扩展点实现类,并且会利用ExtensionLoader类的类加载器来加载扩展点实现类。

  2. 调用loadClass方法对name和扩展点实例进行详细的解析,并且最终把他们放到Map中去。

loadClass方法

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        // 当前接口手动指定了Adaptive类
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            //判断扩展点实现类上是否存在@Adaptive注解, 如果存在,就把该类作为当前的接口代理类, 并放入cachedAdaptiveClass中;
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            // 是一个Wrapper类
            cacheWrapperClass(clazz);
        } else {
            // 需要有无参的构造方法
            clazz.getConstructor();

            // 在文件中没有name,但是在类上指定了Extension的注解上指定了name
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 缓存一下被Activate注解了的类
                cacheActivateClass(clazz, names[0]);

                // 有多个名字, 则分别缓存;
                for (String n : names) {
                    // clazz: name
                    cacheName(clazz, n);
                    // name: clazz
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

    /**
     *判断扩展点实现类上是否存在@Adaptive注解, 如果存在,就把该类作为当前的接口代理类, 并放入cachedAdaptiveClass中
     */
    private void cacheAdaptiveClass(Class<?> clazz) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
        }
    }
   //根据接口类型获取构造方法, 如果类中, 有一个构造方法,且构造方法只有一个参数, 参数类型为 接口类型, 那么该类就是用来保证接口扩展点实现类的 class; 
 private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
        //不存在这个构造方法,就会抛出这个异常, 返回false
            return false;
        }
    }
    /**
  	如果是包装对象, 则 将Class信息,放入cachedWrapperClasses中,cachedWrapperClasses是一个ConcurrenthashSet,是无序的
     */
    private void cacheWrapperClass(Class<?> clazz) {
        if (cachedWrapperClasses == null) {
            cachedWrapperClasses = new ConcurrentHashSet<>();
        }
        cachedWrapperClasses.add(clazz);
    }

loadClass方法做的事:

  1. 当前扩展点实现类是否存在@Adaptive注解, 如果存在,把该类认为是当前接口的默认接口代理类, 并把类存到cacheAdptiveClass属性上;
  2. 当前扩展实现类是否是一个当前接口的Wrapper类, 看当前类中是否存在一个构造方法, 该构造方法只有一个参数, 参数类型为接口类型,如果存在这么一个构造方法,该类就是该接口的Wrapper类,如果是, 将该类加入到cachedWrapperClasses中, cachedWrapperClasses是一个set集合;
  3. 如果不是自适应类,也不是Wrapper类, 判断是否存在@Extension注解, 该注解是否name值,没有则报错;
  4. 如果有多个name,则判断当前扩展点实现类上是否有@Apative注解,如果存在, 则把该类加入cachedActivates中,是一个Map
  5. 遍历多个name, 把每个name和对应的实现类存到extensionClasses中;
    到此, Dubbo加载所有的扩展点实现类的过程就走完了。

最终回到createExtension(String name)中的逻辑, 当前这个接口的所有扩展点实现类都扫描完了之后,就可以根据用户所指定的名字,找到对应的实现类, 然后实例化, 在进行依赖注入;

Dubbo中的IOC/依赖注入

 private T createExtension(String name) {
        // 获取扩展类  {name: Class}  key-Value    接口的所有实现类
        Class<?> clazz = getExtensionClasses().get(name);
    
        try {
            // 实例缓存
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 创建实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // 依赖注入 IOC
            injectExtension(instance);
            return instance;
        } catch (Throwable t) {
          
        }
    }
  1. 获取实例的Class信息,再获取Class所有方法
  2. 遍历所有Method方法
  3. 判断是否为Setter方法,根据方法是否以set开头,且只有一个参数, 且方法类型是public;
  4. 如果是setter方法,判断方法是否被@DisableInject注解修饰,如果被修饰,则不进行注入;
  5. 如果是setter方法,判断方式的参数是否为基本数据类型, 是则不进行注入;
  6. 获取setXxx中的xxx
  7. 根据参数类型或属性名,从objectFactory中获取到对象
  8. 通过反射技术调用set方法进行注入属性值;
private T injectExtension(T instance) {
        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }

                // 利用set方法注入

                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }

                // set方法中的参数类型
                Class<?> pt = method.getParameterTypes()[0];   // Person接口
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 得到setXxx中的xxx
                    String property = getSetterProperty(method);   // person

                    // 根据参数类型或属性名,从objectFactory中获取到对象,然后调用set方法进行注入
                    Object object = objectFactory.getExtension(pt, property); // User.class, user
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
    //判断是否为setter方法
    private boolean isSetter(Method method) {
        return method.getName().startsWith("set")
                && method.getParameterTypes().length == 1
                && Modifier.isPublic(method.getModifiers());
    }
    //获取setter方法对应的属性名;将第四个字符串转化为小写,再与第四个字符后的内容进行拼接,得出属性名;
    private String getSetterProperty(Method method) {
        return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
    }
    

Dubbo中的AOP

Dubbo实现了简单的AOP,利用Wrapper, 如果一个接口的扩展点中包含了多个Wrapper类, 再实例化某个扩展点后,就会利用这些Wrapper类对这个实例进行包装, 例如DubboProtocol实例, 对于Protocol接口还有很多Wrapper, 例如ProtocolFilterWrapper, ProtocolListenerWrapper. 对于当DubooProtocol实例完成IOC后, 就会调用new ProtocolFilterWrapper(DubboProtocol实例)生成一个新的Protocol实例, 再对此实例进行IOC, 结束后,调用protocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol实例,再次IOC, 完成DubboProtocol实例的AOP;

    private T createExtension(String name) {
     //....
            // 依赖注入 IOC
            injectExtension(instance);

            // AOP
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // new CarWrapper(instance)---CarWrapper实例
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));  // new YMapper(new XWrapper(市里的))
                }
            }

            return instance;
        } catch (Throwable t) {
        }
    }

自适应扩展点

自适应扩展点对象,也就是某个接口的代理对象是通过Dubbo内部生成代理类,然后生成代理对象的。
另外还有一个机制生成自适应扩展点, 通过@Apative注解指定某个类为某个接口的代理类。如果指定了,Dubbo再生成某个接口的代理对象的时候, 实际生成就是@Adaptive注解修饰的类实例对象;

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getAdaptiveExtension();

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

createAdaptiveExtension

先获取当前接口的@Adapative注解修饰的对象,再实例化,再进行依赖注入;

    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

getAdaptiveExtensionClass

  1. 获取当前接口的所有扩展类, 如果被@Adaptive注解修饰的Class,会复制cachedAdaptiveClass属性;
  2. 缓存了@Adaptive注解标记的类
  3. 如果某个接口没有手动指定一个Adaptive类,那么就自动生成一个Adaptive类
    private Class<?> getAdaptiveExtensionClass() {
        // 获取当前接口的所有扩展类
        getExtensionClasses(); //
        // 缓存了@Adaptive注解标记的类
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果某个接口没有手动指定一个Adaptive类,那么就自动生成一个Adaptive类
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

createAdaptiveExtensionClass()方法

  1. 通过AdaptiveClassCodeGenerator.generate生成一个类字符串信息(.java文件);
  2. 获取类加载器
  3. 编译
  4. 加载
    private Class<?> createAdaptiveExtensionClass() {
        // cachedDefaultName表示接口默认的扩展类
        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);
    }

new AdaptiveClassCodeGenerator(type, cachedDefaultName)

  1. type 是 接口类信息
  2. cacheDefaultname就是类名字;
    public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
        this.type = type;
        this.defaultExtName = defaultExtName;
    }

AdaptiveClassCodeGenerator#generate()

public class AdaptiveClassCodeGenerator {

    private static final Logger logger = LoggerFactory.getLogger(AdaptiveClassCodeGenerator.class);

    private static final String CLASSNAME_INVOCATION = "org.apache.dubbo.rpc.Invocation";

    private static final String CODE_PACKAGE = "package %s;\n";

    private static final String CODE_IMPORTS = "import %s;\n";

    private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";

    private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";

    private static final String CODE_METHOD_ARGUMENT = "%s arg%d";

    private static final String CODE_METHOD_THROWS = "throws %s";

    private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";

    private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";

    private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";

    private static final String CODE_EXT_NAME_NULL_CHECK = "if(extName == null) "
                    + "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n";

    private static final String CODE_INVOCATION_ARGUMENT_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); "
                    + "String methodName = arg%d.getMethodName();\n";


    private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);\n";

    private static final String CODE_EXTENSION_METHOD_INVOKE_ARGUMENT = "arg%d";

    private final Class<?> type;

    private String defaultExtName;

    public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
        this.type = type;
        this.defaultExtName = defaultExtName;
    }


    /**
     * generate and return class code
     */
    public String generate() {
		
        StringBuilder code = new StringBuilder();
        //插入package信息, 例如package com.xxx
        code.append(generatePackageInfo());
        //插入需要导入的包信息, 例如 import com.xxx;
        code.append(generateImports());
        //插入类的声明
        code.append(generateClassDeclaration());

        // 遍历接口中的方法,生成代理方法
        Method[] methods = type.getMethods();
        for (Method method : methods) {
        //插入方法信息
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        //转换为字符串返回
        return code.toString();
    }
  }

例如Dubbo中, Protocol生成Adaptive类是这样子的:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    
	public void destroy()  {
		throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}

    public int getDefaultPort()  {
		throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}
    
	public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
		if (arg0 == null) 
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
		if (arg0.getUrl() == null) 
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
		
        org.apache.dubbo.common.URL url = arg0.getUrl();
		
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

        if(extName == null) 
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
 		
        return extension.export(arg0);
	}

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {

        if (arg1 == null) throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;

        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);

        return extension.refer(arg0, arg1);
	}
}

Protocol接口中有四个方法中,export和refer方法有@Adaptive注解;


@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    void destroy();

}

但是不是方法上加了@Apdative注解就可以进行代理, 还有条件

  1. 方法无参 , 报错;
  2. 方法有多参, 某个参数类型是URL,可代理
  3. 方法多参, 无URL参数类型, 不代理
  4. 方法多参, 一个参数类中, 有getUrl方法,返回值为URL,可代理;

所以,可以发现,某个接口的Adaptive对象,在调用某个方法时,是通过该方法中的URL参数,通过调用ExtensionLoader.getExtensionLoader(Class).getExtension(extName)得到一个扩展点实现类实例, 调用该实例对应的方法;

URL

Dubbo提供了一个org.apache.dubbo.common.URL类;

  1. 需要传入协议名, IP, 端口号;
        URL url = new URL("dubbo", "localhost", 8080);

Activate扩展点

上文说到,每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例

ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("http://", "localhost", 8080);
url = url.addParameter("cache", "test");

List<Filter> activateExtensions = extensionLoader.getActivateExtension(url, 
                                                      new String[]{"validation"},
                                                      CommonConstants.CONSUMER);
for (Filter activateExtension : activateExtensions) {
	System.out.println(activateExtension);
}

会找到5个Filter

org.apache.dubbo.rpc.filter.ConsumerContextFilter@4566e5bd
org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@1ed4004b
org.apache.dubbo.monitor.support.MonitorFilter@ff5b51f
org.apache.dubbo.cache.filter.CacheFilter@25bbe1b6
org.apache.dubbo.validation.filter.ValidationFilter@5702b3b1

前三个是通过CommonConstants.CONSUMER找到的
CacheFilter是通过url中的参数找到的
ValidationFilter是通过指定的name找到的

在一个扩展点类上,可以添加@Activate注解,这个注解的属性有:

  1. String[] group():表示这个扩展点是属于哪组的,这里组通常分为PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使用
  2. String[] value():表示的是URL中的某个参数key,当利用getActivateExtension方法来寻找扩展点时,如果传入的url中包含的参数的所有key中,包括了当前扩展点中的value值,那么则表示当前url可以使用该扩展点。

感想

对Dubbo的SPI流程有了深一步的理解, 别人写个dubbo的demo的例子就觉得dubbo学会了,于我而言, 这连入门都算不上,知其然,知其所以然, 刨根根底, 坚持坚持;

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值