前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo源码之SPI AdaptiveExtension和Wrapper

dubbo源码之SPI AdaptiveExtension和Wrapper

作者头像
山行AI
发布2019-07-16 17:10:52
9530
发布2019-07-16 17:10:52
举报
文章被收录于专栏:山行AI山行AI

dubbo 中SPI 拓展这一节中,@Adaptive是相当重要的一部分,ExtensionLoader构造器中会调用getAdaptiveExtension()方法触发为当前扩展类型生成适配器,用到的是动态代理技术。dubbo中是通过javassist技术生成的动态代理类。与传统的jdk动态代理、cglib不同,javassist提供封装后的API对字节码进行间接操作,简单易用,不关心具体字节码,灵活性更高,且处理效率也较高,是dubbo默认的编译器。

1. 前言

在之前的推文中我们知道,dubbo有很多SPI的拓展点,而ExtensionLoader又是dubbo SPI拓展点的加载器。这篇文章中我们将以ExtensionLoader为切入点来对dubbo的SPI机制进行分析。dubbo中的SPI拓展点的主要位置在:

  • META-INF/services/ services拓展点目录
  • META-INF/dubbo/ 一般自定义的放在这里
  • META-INF/dubbo/internal/ 内部拓展点目录

在META-INF/dubbo/internal/目录里有主要有:

2. 流程分析

在com.alibaba.dubbo.config.ServiceConfig类中有这样几个属性:

这里是通过拓展点加载器ExtensionLoader来加载Protocol和ProxyFactory,我们来针对Protocol为例来看一看具体流程: Protocol类的定义为:

Protocol的spi拓展文件如下:

在它的export和refer方法上都加了@Adaptive注解。下面进行具体分析。

  1. private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
  • com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader方法:
代码语言:javascript
复制
  public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {        if (type == null)            throw new IllegalArgumentException("Extension type == null");        if(!type.isInterface()) {            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");        }        if(!withExtensionAnnotation(type)) {            throw new IllegalArgumentException("Extension type(" + type +                     ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");        }        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;    }

这里是获取(先在缓存中取,如果缓存中取不到就新建,然后放入缓存并返回)指定type的ExtensionLoader的代码。

  • com.alibaba.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension方法:
代码语言:javascript
复制
public T getAdaptiveExtension() {        Object instance = cachedAdaptiveInstance.get();        if (instance == null) {            if(createAdaptiveInstanceError == null) {                synchronized (cachedAdaptiveInstance) {//double check 获取protocol 类对应的adaptiveExtension实例(先取缓存,缓存中没有则新建并放入缓存和返回)                    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;    }

这一段是double check 获取protocol 类对应的adaptiveExtension实例(先取缓存,缓存中没有则新建并放入缓存和返回),在SPI介绍的那一篇有提到过这个。下面我们看一下createAdaptiveExtension(),因为第一次进来肯定是要走这个方法的:

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

将getAdaptiveExtensionClass().newInstance()生成的实例通过injectExtension进行注入(IOC)。这里注入的是根据cachedAdaptiveClass生成的实例,请关注下文对cachedAdaptiveClass的说明。进入getAdaptiveExtensionClass方法:

代码语言:javascript
复制
private Class<?> getAdaptiveExtensionClass() {    getExtensionClasses();//加载拓展文件中的classes    if (cachedAdaptiveClass != null) {        return cachedAdaptiveClass;    }    return cachedAdaptiveClass = createAdaptiveExtensionClass();//创建AdaptiveExtensionClass}
  1. 先来看一看getExtensionClasses()方法:
代码语言:javascript
复制
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;    }

double check 加载classes的过程,接下来看一看loadExtensionClasses()的实现:

代码语言:javascript
复制
 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/";// 此方法已经getExtensionClasses方法同步过。    private Map<String, Class<?>> loadExtensionClasses() {        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));                }                if(names.length == 1) cachedDefaultName = names[0];            }        }        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);        loadFile(extensionClasses, DUBBO_DIRECTORY);        loadFile(extensionClasses, SERVICES_DIRECTORY);        return extensionClasses;    }

这个方法主要从上面三个spi目录中加载文件,并将class文件分类放好,具体分类的过程在loadFile方法中:

代码语言:javascript
复制
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释 
* private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {        String fileName = dir + type.getName();//会从目录的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) {                                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)) {//如果加载到clazz不是type的子类,则抛异常                                                throw new IllegalStateException("Error when load extension class(interface: " +                                                        type + ", class line: " + clazz.getName() + "), class "                                                         + clazz.getName() + "is not subtype of interface.");                                            }                                            if (clazz.isAnnotationPresent(Adaptive.class)) {//如果类上面加了@Adaptive注解,则将这个类设置成cachedAdaptiveClass                                                if(cachedAdaptiveClass == null) {                                                    cachedAdaptiveClass = clazz;                                                } else if (! cachedAdaptiveClass.equals(clazz)) {//如果这个clazz和之前设置的cachedAdaptiveClass不一致则报错。也就是说一个像Protocol这样的父接口,它的实现类上只能有一个加@Adaptive注解                                                    throw new IllegalStateException("More than 1 adaptive class found: "                                                            + cachedAdaptiveClass.getClass().getName()                                                            + ", " + clazz.getClass().getName());                                                }                                            } else {                                                try {                                                    clazz.getConstructor(type);//如果类上面没有加@Adaptive注解,则尝试获取这个类上有无带有父接口(type)类型的参数的构造方法,如果有那么这个实现类就属于使用装饰器模式装饰过的wrappers,放入cachedWrapperClasses中去                                                    Set<Class<?>> wrappers = cachedWrapperClasses;                                                    if (wrappers == null) {                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();                                                        wrappers = cachedWrapperClasses;                                                    }                                                    wrappers.add(clazz);                                                } catch (NoSuchMethodException e) {                                                    //没有对应的构造方法,则调用无参的构造方法                                                    clazz.getConstructor();                                                    if (name == null || name.length() == 0) {                                                        name = findAnnotationName(clazz);                                                        if (name == null || name.length() == 0) {                                                            //实现类的className要比type的大                                                            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) {                                                           //如果加了Activate注解则放入cachedActivates目录中                                                        Activate activate = clazz.getAnnotation(Activate.class);                                                        if (activate != null) {                                                            cachedActivates.put(names[0], activate);                                                        }                                                        for (String n : names) {                                                            if (! cachedNames.containsKey(clazz)) {                                                                //如果没有Activate注解则放入cachedNames                                                                cachedNames.put(clazz, n);                                                            }                                                            Class<?> c = extensionClasses.get(n);                                                            if (c == null) {                                                                //放入extensionClasses中去                                                                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);        }    }
*/
  • 会从目录的type.getName对应的目录中去加载对应的文件。因为本例传入的type是Protocol,所以会查看META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal中的com.alibaba.dubbo.rpc.Protocol文件中的所有的类。
  • 如果加载到clazz不是type的子类,则抛异常。
  • 如果加载到的类上面加了@Adaptive注解,则将这个类设置成cachedAdaptiveClass。这里需要说明一下:

它的三个实现类:只有AdaptiveExtensionFactory是加了@Adaptive注解的,注意这里最后也是调的loader的getExtension方法(关于这个方法见下文有详细描述)。

这种情况下在上面的getAdaptiveExtensionClass方法中就会走cachedAdaptiveClass != null的分支,直接返回cachedAdaptiveClass, 也就是说生成的ExtensionFactory实例是AdaptiveExtensionFactory。如果没有实现类上加有@Adaptive注解,则会走下面的方法,使用父接口去创建Adaptive,如Protocol接口:

代码语言:javascript
复制
   private Class<?> getAdaptiveExtensionClass() {       getExtensionClasses();//加载拓展文件中的classes       if (cachedAdaptiveClass != null) {//不为null直接返回           return cachedAdaptiveClass;       }       return cachedAdaptiveClass = createAdaptiveExtensionClass();//创建AdaptiveExtensionClass   }
  • 如果加载到的类的clazz和之前设置的cachedAdaptiveClass不一致则报错。也就是说一个像Protocol这样的父接口,它的实现类上只能有一个加@Adaptive注解。
  • 如果类上面没有加@Adaptive注解,则尝试获取这个类上有无带有父接口(type)类型的参数的构造方法,如果有那么这个实现类就属于使用装饰器模式装饰过的wrappers,放入cachedWrapperClasses中去。如:
  • 如果不符合Adaptive和Wrapper的条件,则调用实现类无参的构造方法,同时要求实现类的className要比type的大,如果加了Activate注解则放入cachedActivates目录中(方便com.alibaba.dubbo.common.extension.ExtensionLoader#getActivateExtension(com.alibaba.dubbo.common.URL, java.lang.String)方法调用), 不管有没有Activate注解都放入cachedNames(方便com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionName(java.lang.Class)方法获取)。
  • 加载到的class都会放入到extensionClasses中去。这里有必要先看下getExtension(String name)方法,这个方法是生成的以Protocol为例,Protocol$Adaptive类中的方法获取拓展点的方法:

不同的是对于wrapperClasses这里会使用有参构造的方式进行构建实例。接下来loadExtensionClasses方法是上面分析过的。这就是表明这里是将加载到的classes进行缓存,然后在getExtension方法的时候使用。

  1. 来看一看createAdaptiveExtensionClass方法的实现
代码语言:javascript
复制
 private Class<?> createAdaptiveExtensionClass() {        String code = createAdaptiveExtensionClassCode();//要生成的adaptive类的代码        ClassLoader classLoader = findClassLoader();//找类加载器        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();        return compiler.compile(code, classLoader);    }

关于Compiler:

com.alibaba.dubbo.common.extension.ExtensionLoader#getDefaultExtension方法:

代码语言:javascript
复制
/**     * 返回缺省的扩展,如果没有设置则返回<code>null</code>。     */    public T getDefaultExtension() {        getExtensionClasses();        if(null == cachedDefaultName || cachedDefaultName.length() == 0                || "true".equals(cachedDefaultName)) {            return null;        }        return getExtension(cachedDefaultName);    }

关于cachedDefaultName:

可见,默认用的是javassist compiler。

对于createAdaptiveExtensionClassCode方法的作用主要是生成adaptive代理类的代码,然后交给Compiler进行编译生成class文件,它的部分代码如下:

  • 如果整个类完全没有Adaptive方法,则不需要生成Adaptive类
  • 如果有部分Adaptive方法,则对Adaptive方法进行代理,对非Adaptive方法则抛出异常。
  • 类的命名规则:public class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " ,也就是类(注意是type类,也就是Protocol)的简称+$Adpative,type.getCanonicalName()是父接口签名。
  • type.isAssignableFrom(clazz)用于判断是新加载clazz是否是type的子类,不是就报错。
  • debug之后得到的Protocol生成的Adaptive代理类代码为:
代码语言:javascript
复制
package com.alibaba.dubbo.rpc;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {    // 没有打上@Adaptive的方法如果被调到抛异常    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!");    }    // 没有打上@Adaptive的方法如果被调到抛异常    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!");    }    // 接口中export方法打上@Adaptive注册    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)//url属性不能为null            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");        com.alibaba.dubbo.common.URL url = arg0.getUrl();        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());//从url中获取到实际使用的拓展点的name,也就是META-INF目录下配置的kv中的key值        if (extName == null)//extentsion的名称不能为null            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");        // 利用dubbo服务查找机制根据名称找到具体的扩展点实现        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);    }    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;        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());//从url中获取到实际使用的拓展点的name,也就是META-INF目录下文件中配置的kv中的key值        if (extName == null)            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");        //通过extName获取对应的拓展点        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);        return extension.refer(arg0, arg1);    }}

extName是实际需要调用的拓展点的名称,然后可以看到在$Adaptive类中最终是使用getExtension方法来获取真正使用的拓展点的,关于getExtension方法在上面有详细的讲解。

这时候可以回过来看一看com.alibaba.dubbo.config.ServiceConfig中的几个方法,以exportLocal方法为例:

代码语言:javascript
复制
   private void exportLocal(URL url) {        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {            URL local = URL.valueOf(url.toFullString())                    .setProtocol(Constants.LOCAL_PROTOCOL)                    .setHost(NetUtils.LOCALHOST)                    .setPort(0);            // modified by lishen            ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));            Exporter<?> exporter = protocol.export(                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));            exporters.add(exporter);            logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");        }    }

在构建invoker时url和extName都是事先处理好的,这个不清楚的可以去看我写的另一篇文章:dubbo源码之Proxy、Transporter和Exchanger执行过程。

到这里再回过头来看com.alibaba.dubbo.common.extension.ExtensionLoader#createAdaptiveExtension方法:

代码语言:javascript
复制
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);    }}

上面通过getAdaptiveExtensionClass()获得到了对应的Adaptive的代理的大Class对象,然后通过newInstance方法生成一个实例,再通过injectExtension方法进行注入。

关于injectExtension:

代码语言:javascript
复制
private T injectExtension(T instance) {    try {        if (objectFactory != null) {            for (Method method : instance.getClass().getMethods()) {                if (method.getName().startsWith("set")                        && method.getParameterTypes().length == 1                        && Modifier.isPublic(method.getModifiers())) {// 处理所有set方法                    Class<?> pt = method.getParameterTypes()[0];// 获取set方法参数类型                    try {                        // 获取setter对应的property名称                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";                        Object object = objectFactory.getExtension(pt, property); // 根据类型,名称信息从ExtensionFactory获取,实际上是AdaptiveExtensionFactory                        if (object != null) { // 如果不为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中的ioc功能,也就是对一个bean 实例进行属性的注入,但是这个属性需要满足一定的条件:有规范的set方法,然后必须是拓展点类型的属性(在extensionFactory中要能找到)。

3. 总结

  • 调用示例:ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(),ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
  • 先是加载META-INF下三个目录中对应的文件com.alibaba.dubbo.rpc.Protocol和com.alibaba.dubbo.common.extension.ExtensionFactory中的每一行定义的实现类的class信息。
  • 如果类实现类上如果标注有@Adaptive注解时,则直接采用该实现类生成$Adaptive代理类,如AdaptiveExtensionFactory。如果类上没有@Adaptive注解则先如果类上面没有加@Adaptive注解,则尝试获取这个类上有无带有父接口(type)类型的参数的构造方法, 如果有那么这个实现类就属于使用装饰器模式装饰过的wrappers,放入cachedWrapperClasses中去,wrapper模式的特殊点就是构建实例的时候调用的是有参的构造方法,传入一个拓展点对象。这种没有加@Adaptive注解的,不管符不符合wrapper模式,都会使用接口生成$Adaptive代理类,如Protocol。
  • 生成代理类使用的是javaassit技术,代理类会从invoker的url中获取到最终需要调用的拓展点实现类的即(extName,实际调用的拓展点的名称)是调用getExtension去查找对应的拓展点(也就是META-INF目录下文件中配置的kv中的key值)。
  • injectExtension的作用相当于ioc,也就是对传入的拓展点实例中还包括拓展点类型的属性时,如果有set方法就可以进行注入。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. 流程分析
  • 3. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档